1. 訊號量基本術語
現代電腦系統中,多個進程可以並發執行,進程間必然存在共用資源和相互合作的問題。
同步主要是使用者多個進程相互協作,共同完成任務,是進程間的直接制約問題;互斥則主要是為了多個進程分時使用有限的資源。
訊號量(semaphore)是1965年由荷蘭人Dijkstra提出的一種卓有成效的進程間同步及互斥工具。訊號量在作業系統中實現時一般作為一個整數變數,這種訊號量稱為整型訊號量。訊號量S的物理意義:
S >= 0表示某資源的可用數;
S<0 其絕對值表示阻塞隊列中等待該資源的進程數目;
訊號量的兩個操作方法是PV,P操作為S=S-1;表示申請一個資源,V操作為S=S+1;表示釋放一個資源。因為對整數的加減1操作是在一個指令完成的,而硬體能夠保證中斷只能發生在指令之間,所以PV操作不會被中斷打擾執行結果。
P
{
S = S-1;
if(s<0)
Wait(S); --------當前進程進入等待隊列等待
}
V
{
S=S+1;
if(S<=0)
Resume(S); ----------喚醒等待隊列中的一個進程
}
2.利用訊號量實現互斥
初始化訊號量mutex = 1; 當進程進入臨界區時執行P操作,退出臨界區時執行V操作。
P(mutex)
臨界區代碼;(操作互斥資源)
V(mutex)
3. 利用訊號量實現同步
此時可以把訊號想象成代表一個訊息。當S=0表示表示訊息未產生;當S>0則表示訊息已經產生。例如:
(1)單緩衝區的生產者和消費者問題。
生產者進程P1不斷地生產產品送入緩衝區,消費者進程P2不斷地從緩衝區中提取產品消費。為了實現P1與P2進程間的同步,需要設定一個訊號量S1,並且初始化為1,表示緩衝區為空白,可以將產品放入緩衝區中;還需要設定另一個另一個訊號量S2,初始值為0,表示緩衝區沒有產品,可以提取消費。
P1:生產一個產品--->P(S1)測試緩衝區是否為空白----->產品送緩衝區---->V(S2)設定有產品---->重複
P2: P(S2)測試是否有產品----->從緩衝區取出產品------->V(S1)設定緩衝區為空白------->消費--->重複
(2)多緩衝區同步問題
設有一個生產者和一個消費者,緩衝區可以存放n件物品,生產者不斷地生產產品,消費者不斷地消費產品。
設定3個訊號量,S, S1,S2。其中S是一個互斥訊號量初值為1,對緩衝區資源進行互斥控制,S1表示是否可以將物品放入緩衝區,初值為n,S2表示緩衝區中是否有物品,初值為0。同步過程如下:
P1:生產一個產品----->P(S1)--->P(S)--->產品送緩衝區--->V(S)---->V(S2)
P2:P(S2)------>P(S)--->從緩衝區取出一個產品----->V(S)----->V(S1)----->消費
附上在Windows和Unix平台使用訊號量的源碼:
至於什麼是訊號量(Semaphore),網上相關資料多得很,一搜一大把,我就不做重複勞動了。只是把關於使用訊號量的樣本程式貼一下當作筆記,萬一能對大家有點點協助那就更好了。Windows 平台的代碼來源於 MSDN,Unix 平台的代碼是 Google 來的。</p><p>Part 1 - Windows 平台訊號量(Semaphore)樣本程式<br />#include <stdio.h><br />#include <Windows.h></p><p>#define MAX_SEM_COUNT 10<br />#define THREADCOUNT 12</p><p>HANDLE ghSemaphore;</p><p>DWORD WINAPI ThreadProc( LPVOID );</p><p>void main(int argc, char* argv[])<br />{<br /> HANDLE aThread[THREADCOUNT];<br /> DWORD ThreadID;<br /> int i;</p><p> // Create a semaphore with initial and max counts of MAX_SEM_COUNT</p><p> ghSemaphore = CreateSemaphore(<br /> NULL, // default security attributes - lpSemaphoreAttributes是訊號量的安全屬性<br /> MAX_SEM_COUNT, // initial count - lInitialCount是初始化的訊號量<br /> MAX_SEM_COUNT, // maximum count - lMaximumCount是允許訊號量增加到最大值<br /> NULL); // unnamed semaphore - lpName是訊號量的名稱</p><p> if (ghSemaphore == NULL)<br /> {<br /> printf("CreateSemaphore error: %d/n", GetLastError());<br /> return;<br /> }</p><p> // Create worker threads</p><p> for( i=0; i <THREADCOUNT; i++ )<br /> {<br /> aThread[i] = CreateThread(<br /> NULL, // default security attributes<br /> 0, // default stack size<br /> (LPTHREAD_START_ROUTINE) ThreadProc,<br /> NULL, // no thread function arguments<br /> 0, // default creation flags<br /> &ThreadID); // receive thread identifier</p><p> if( aThread[i] == NULL )<br /> {<br /> printf("CreateThread error: %d/n", GetLastError());<br /> return;<br /> }<br /> }</p><p> // Wait for all threads to terminate</p><p> WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);</p><p> // Close thread and semaphore handles</p><p> for( i=0; i <THREADCOUNT; i++ )<br /> CloseHandle(aThread[i]);</p><p> CloseHandle(ghSemaphore);<br />}</p><p>DWORD WINAPI ThreadProc( LPVOID lpParam )<br />{<br /> DWORD dwWaitResult;<br /> BOOL bContinue=TRUE;</p><p> while(bContinue)<br /> {<br /> // Try to enter the semaphore gate.<br /> dwWaitResult = WaitForSingleObject(<br /> ghSemaphore, // handle to semaphore<br /> 0L); // zero-second time-out interval</p><p> switch (dwWaitResult)<br /> {<br /> // The semaphore object was signaled.<br /> case WAIT_OBJECT_0:<br /> // TODO: Perform task<br /> printf("Thread %d: wait succeeded/n", GetCurrentThreadId());</p><p> // Ensure a thread performs only once<br /> bContinue=FALSE;</p><p> // Simulate thread spending time on task<br /> Sleep(5);</p><p> // Release the semaphore when task is finished</p><p> if (!ReleaseSemaphore(<br /> ghSemaphore, // handle to semaphore - hSemaphore是要增加的訊號量控制代碼<br /> 1, // increase count by one - lReleaseCount是增加的計數<br /> NULL) ) // not interested in previous count - lpPreviousCount是增加前的數值返回<br /> {<br /> printf("ReleaseSemaphore error: %d/n", GetLastError());<br /> }<br /> break; </p><p> // The semaphore was nonsignaled, so a time-out occurred.<br /> case WAIT_TIMEOUT:<br /> printf("Thread %d: wait timed out/n", GetCurrentThreadId());<br /> break;<br /> }<br /> }<br /> return TRUE;<br />}</p><p>Part 2 - Unix 平台訊號量(Semaphore)樣本程式<br />/* semabinit.c - initialize a semaphore for use by programs sema and semb */</p><p>#include <sys/types.h><br />#include <sys/ipc.h><br />#include <sys/sem.h><br />#include <stdio.h></p><p>/* The semaphore key is an arbitrary long integer which serves as an<br />external identifier by which the semaphore is known to any program<br />that wishes to use it. */</p><p>#define KEY (1492)</p><p>void main()<br />{<br />int id; /* Number by which the semaphore is known within a program */</p><p>/* The next thing is an argument to the semctl() function. Semctl()<br />does various things to the semaphore depending on which arguments<br />are passed. We will use it to make sure that the value of the<br />semaphore is initially 0. */</p><p>union semun {<br />int val;<br />struct semid_ds *buf;<br />ushort * array;<br />} argument;</p><p>argument.val = 0;</p><p>/* Create the semaphore with external key KEY if it doesn't already<br />exists. Give permissions to the world. */</p><p>id = semget(KEY, 1, 0666 | IPC_CREAT);</p><p>/* Always check system returns. */</p><p>if(id <0)<br />{<br />fprintf(stderr, "Unable to obtain semaphore. ");<br />exit(0);<br />}</p><p>/* What we actually get is an array of semaphores. The second<br />argument to semget() was the array dimension - in our case<br />1. */</p><p>/* Set the value of the number 0 semaphore in semaphore array<br /># id to the value 0. */</p><p>if( semctl(id, 0, SETVAL, argument) <0)<br />{<br />fprintf( stderr, "Cannot set semaphore value. ");<br />}<br />else<br />{<br />fprintf(stderr, "Semaphore %d initialized. ", KEY);<br />}<br />}</p><p>/* Semaphore example program a (sema.c) */<br />/* We have two programs, sema and semb. Semb may be initiated at any<br />time, but will be forced to wait until sema is executed. Sema and<br />semb do not have to be executed by the same user! */</p><p>#include <stdio.h><br />#include <sys/types.h><br />#include <sys/ipc.h><br />#include <sys/sem.h></p><p>#define KEY (1492)<br />/* This is the external name by which the semaphore is known to any<br />program that wishes to Access it. */</p><p>void main()<br />{<br />int id; /* Internal identifier of the semaphore. */<br />struct sembuf operations[1];<br />/* An "array" of one operation to perform on the semaphore. */</p><p>int retval; /* Return value from semop() */</p><p>/* Get the index for the semaphore with external name KEY. */<br />id = semget(KEY, 1, 0666);<br />if(id <0)<br />/* Semaphore does not exist. */<br />{<br />fprintf(stderr, "Program sema cannot find semaphore, exiting. ");<br />exit(0);<br />}</p><p>/* Do a semaphore V-operation. */<br />printf("Program sema about to do a V-operation. ");</p><p>/* Set up the sembuf structure. */<br />/* Which semaphore in the semaphore array : */<br />operations[0].sem_num = 0;<br />/* Which operation? Add 1 to semaphore value : */<br />operations[0].sem_op = 1;<br />/* Set the flag so we will wait : */<br />operations[0].sem_flg = 0;</p><p>/* So do the operation! */<br />retval = semop(id, operations, 1);</p><p>if(retval == 0)<br />{<br />printf("Successful V-operation by program sema. ");<br />}<br />else<br />{<br />printf("sema: V-operation did not succeed. ");<br />perror("REASON");<br />}<br />}</p><p>/* Think carefully about what the V-operation does. If sema is executed<br />twice, then semb can execute twice. */</p><p>/* Semaphore example program b (semb.c) */<br />/* We have two programs, sema and semb. Semb may be initiated at any<br />time, but will be forced to wait until sema is executed. Sema and<br />semb do not have to be executed by the same user! */</p><p>/* HOW TO TEST:<br />Execute semb &<br />The & is important - otherwise you would have have to move to<br />a different terminal to execute sema.</p><p>Then execute sema.<br />*/</p><p>#include <stdio.h><br />#include <sys/types.h><br />#include <sys/ipc.h><br />#include <sys/sem.h></p><p>#define KEY (1492)<br />/* This is the external name by which the semaphore is known to any<br />program that wishes to access it. */</p><p>void main()<br />{<br />int id; /* Internal identifier of the semaphore. */<br />struct sembuf operations[1];<br />/* An "array" of one operation to perform on the semaphore. */</p><p>int retval; /* Return value from semop() */</p><p>/* Get the index for the semaphore with external name KEY. */<br />id = semget(KEY, 1, 0666);<br />if(id <0)<br />/* Semaphore does not exist. */<br />{<br />fprintf(stderr, "Program semb cannot find semaphore, exiting. ");<br />exit(0);<br />}</p><p>/* Do a semaphore P-operation. */<br />printf("Program semb about to do a P-operation. ");<br />printf("Process id is %d ", getpid());</p><p>/* Set up the sembuf structure. */<br />/* Which semaphore in the semaphore array : */<br />operations[0].sem_num = 0;<br />/* Which operation? Subtract 1 from semaphore value : */<br />operations[0].sem_op = -1;<br />/* Set the flag so we will wait : */<br />operations[0].sem_flg = 0;</p><p>/* So do the operation! */<br />retval = semop(id, operations, 1);</p><p>if(retval == 0)<br />{<br />printf("Successful P-operation by program semb. ");<br />printf("Process id is %d ", getpid());<br />}<br />else<br />{<br />printf("semb: P-operation did not succeed. ");<br />}<br />}</p><p>/* Think carefully about what the V-operation does. If sema is executed<br />twice, then semb can execute twice. */<br />