進程同步、進程互斥的兩種機制,這里簡單總結是其中的信號量機制(Semaphores)。
建議: 不少概念涉及到進程同步的內容,所以查看這個內容時可以結合或提前參考進程同步的內容,
信號量機制是 荷蘭學者 Dijkstra 提出的,這是一種卓有成效的進程同步工具。發展:整型信號量->記錄型信號量->AND 型信號量->信號量集,依次講解下。
四種信號量
整型信號量
定義:把整型信號量定義為一個用於表示資源數目的整型量 S,它與一般整型 量不同,除初始化外,僅能通過兩個標准的原子操作(Atomic Operation) wait(S)和 signal(S)來訪問。
PV操作由P操作原語和V操作原語組成(原語是執行時不可中斷,PV操作即定義中的原子操作wait(S)和signal(S))。因此這個操作通常被叫做PV操作。
注:在荷蘭文中,通過叫passeren,釋放叫vrijgeven,PV操作因此得名。wait(S)即申請資源,signal(S)釋放資源。
描述:
wait(S) { while(S <= 0); S--; } signal(S) { S++; }
不足:當S<=0時,就要不斷檢測。陷入“忙等”狀態,不符合讓權等待。(在進程同步總結中有詳細說明)
記錄型信號量
在整型信號量基礎上,增加了一個進程鏈表指針L,鏈接所有等待的進程。
數據結構定義描述如下:一個整型變量value表示資源數目,進程鏈表指針L。
typedef struct{ int value; struct process *L; } semaphore;
相應的,wait(S)和signal(S)操作過程就是:
當wait(S)申請資源時,S.value減1(為負數時的絕對值即等待進程的數目)。若S.value<0時表示資源耗盡了,進程使用block原句 自我阻塞,放棄處理機,插入到等待進程鏈表中S.L。這就符合了讓權等待,避免了“忙等”。
當signal(S)釋放資源,S.value加1。若加 1 后仍是 S.value≤0,則表示在該鏈表L中,仍有等待該資源的進程被阻塞,故還應 調用 wakeup 原語,將 S.L 鏈表中的第一個等待進程喚醒。
如果 S.value 的初值為 1,表示只允許一個進程訪問臨界資源,此時的信號量轉化為互斥信號量,用於進程互斥。
void wait(semaphore S) { S.value--; if(S.value<0) { block(S.L); } } void signal(semaphore S) { S.value++; if(S.value<=0){ wakeup(S.L); } }
不足:同整型信號量一樣,都是正對的是共享一種臨界資源。 若一個進程需要兩個或更多資源后才可執行,就會出現死鎖的可能,共享資源越多,進程死鎖可能性越大。
比如:有兩個進程A、B。兩個臨界資源D、E,互斥信號量(初值為1)分別是Dmutex、Emutex。
按下面執行次序,A獲得了D等待E,B獲得了E等待D,就處於了僵持狀態,無外界干預,A、B就陷入了死鎖狀態。共享資源越多,進程死鎖可能越大。
process A: wait(Dmutex); 於是 Dmutex=0
process B: wait(Emutex); 於是 Emutex=0
process A: wait(Emutex); 於是 Emutex=-1 A 阻塞
process B: wait(Dmutex); 於是 Dmutex=-1 B 阻塞
AND 型信號量
如上所述,整型信號量和記錄型信號量當共享多種臨界資源時都容易引起死鎖問題。AND型信號量能夠避免上述死鎖情況的出現。
AND同步機制:要么把進程在整個運行過程中所請求的資源全部分配到進程(進程使用完成后一起釋放),要么一個也不分配。
描述:
wait():申請資源S1-Sn,當碰到Si不滿足后,則執行else里,將進程加入到Si關聯的等待隊列中 而進程指針移向wait()開始。wait()是原語,不可中斷。
wait(S1,S2,...,Sn) { if(S1>=1 and … and Sn>=1) { for(i:=1 to n) Si--; } else { place the process in the waiting queue associated with the first Si found with Si<1,and set the program count of this process to the beginning of Swait operation } }
signal():釋放資源S1-Sn,將S1-Sn關聯的等待隊列清空 並調入就緒隊列。
signal(S1,S2,...,Sn) { for(i:=1 to n) { Si++; Remove all the process waiting in the queue associated with Si into the ready queue. } }
不足:整型信號量、記錄型信號量局限在只共享一種臨界資源。AND型信號量局限在單種(一種)臨界資源為1,若單種資源為N(N>=1)時,AND型信號量的wait()和signal()就需操作N次,這是很低效的。
但很多情況是一個進程可能申請多種臨界資源且某種臨界資源數目大於1的。
信號量集
如上所述。當需要單種資源為N(N>=1),AND型操作 wait()和signal()需要操作N次,低效。
除低效問題,還考慮情景:當某種資源數低於某一下限時變不在分配,因此每次都需檢測該資源數是否大於下限值。
從而衍生出了信號量集:
操作可描述如下,其中 S 為信號量,d 為需求值,而 t 為下限值。
wait():申請S1-Sn, Si需要di個,而資源Si下限為ti。當S1-Sn中,Si不滿足下限值ti后,就不能分配了。進入else,進程加入到等待隊列,指針移向wait()開始位置。
wait(S1,t1,d1,…,Sn,tn,dn) { if (S1>=t1 and … and Sn>=tn){ for (i:=1 to n) Si:=Si-di; } else{ Place the executing process in the waiting queue of the first Si with Si<ti and set its program counter to the beginning of the wait Operation. } }
signal():釋放資源S1-Sn,Si釋放di個,S1-Sn關聯的等待隊列清空 並調入就緒隊列。
signal(S1,d1,…,Sn,dn) { for i:=1 to n do { Si:=Si+di; Remove all the process waiting in the queue associated with Si into the ready queue } }
“信號量集”的幾種特殊情況:
(1) Swait(S,d,d)。此時在信號量集中只有一個信號量 S,每次申請 d 個資 源,當現有資源數少於d 時,不予分配。
(2) Swait(S,1,1)。此時的信號量集已蛻化為一般的記錄型信號量(S>1 時)或互斥信號 量(S=1 時)。
(3) Swait(S,1,0)。這是一種很特殊且很有用的信號量操作。當 S>=1 時,允許多個進程進入某特定區;當 S 變為 0 后,將阻止任何進程進入特定區。換言之,它相當於一個可
控開關。
信號量應用
實現進程互斥
為使多個進程能互斥地訪問某臨界資源,只須為該資源設置一互斥信號量 mutex,並設其初始值為 1,然后將各進程訪問該資源的臨界區 置於 wait(mutex)和 signal(mutex)操作之間即可。
Var mutex: semaphore:=1;
process 1: begin repeat wait(mutex); critical section signal(mutex); remainder seetion until false; end process 2: begin repeat wait(mutex); critical section signal(mutex); remainder section until false; end
在利用信號量機制實現進程互斥時應注意,wait(mutex)和 signal(mutex)必須成對地出現。 缺少 wait(mutex)將會導致系統混亂,不能保證對臨界資源的互斥訪問;而缺少 signal(mutex)將會使臨界資源永遠不被釋放,從而使因等待該資源而阻塞的進程不能被喚醒。
利用信號量實現前趨關系
設有兩個並發執行的進程 P1 和 P2。P1 中有語句 S1;P2 中有語句 S2。
我們希望在 S1 執行后再執行 S2。為實現這種前趨關系: S1->S2。
我們只須使進程 P1 和 P2 共享一個公用信號量 S,並賦予其初值為 0,將 signal(S)操作放在語句 S1 后面;而在 S2 語句前面插入 wait(S)操作,即
在進程 P1 中
S1;
signal(S);
在進程 P2 中
wait(S);
S2;
由於 S 被初始化為 0,這樣,若 P2 先執行必定阻塞,只有在進程 P1 執行完 S1;signal(S);操作后使 S 增為 1 時,P2 進程方能執行語句 S2 成功。
這是最基礎的實現,在此基礎上可以實現更多進程間更復雜的前后驅關系,需要多個公共信號量協同操作。