併發程式設計之訊號量與互斥鎖——學習筆記
一、訊號量是什麼
訊號量(semaphore)是作業系統用來解決併發中的互斥和同步問題的一種方法。 訊號量是一個與佇列有關的整型變數,其資料結構為一個值和一個指標,指標指向等待該訊號量的下一個程序。你可以把它想象成一個數後面拖著一條排隊的佇列,那訊號量拖著的那個佇列就是用來放正在排隊想要使用這一資源的程序,如圖:
那訊號量的值S代表什麼意思呢?訊號量的值與相應資源的使用情況有關:
S>0:當前有可用資源,表示當前可用資源的數量為S;
S=0:資源都被佔用,表示當前可用資源的數量為0;
S<0:資源都被佔用,其絕對值表示等待使用該資源的程序個數
注意,訊號量的值僅能由訊號量機制來改變,即利用pv操作來對訊號量進行處理。
一般來說,訊號量S>=0時,S表示可用資源的數量。執行一次P操作意味著請求分配一個單位資源,因此S的值減1;當S<0時,表示已經沒有可用資源,請求者必須等待別的程序釋放該類資源,它才能執行下去。而執行一個V操作意味著釋放一個單位資源,因此S的值加1;若S<=0時,表示有某些程序正在等待該資源,因此要喚醒一個等待狀態的程序,使之執行下去。
二、訊號量虛擬碼
1 struct semaphore{ 2 int count; 3 queueType queue;4 }; 5 6 void semWait(semaphore s){ 7 s.count--; 8 if(s.count < 0){ 9 /*place this process in s.queue*/; 10 /*block this process*/; 11 } 12 } 13 14 void semSignal(semaphore s){ 15 s.count++; 16 if(s.count <= 0){ 17 /*remove a process P from s.queue*/;18 /*place a process P on ready list*/; 19 } 20 }
在程式碼中我們可以看到有兩個對訊號量的count值和阻塞佇列的操作,一個是semWait,一個是semSignal,前者也被稱為P操作,後者也被稱為V操作。
semWait:p操作(wait):申請一個單位資源,程序進入;
經典虛擬碼:
1 wait(S){ 2 while(s<=0);//如果沒有資源則會迴圈等待; 3 S-- ; 4 }
semSignal:v操作(signal):釋放一個單位資源,程序出來;
經典虛擬碼:
1 signal(S){ 2 S++ ; 3 }
當申請資源的時候,資源數count值--,我們注意到資源數,如果在--之後<0,那麼這個這個程序就會加入到等待佇列。
為什麼這個條件設定成<0呢?其實很好理解,當這個資源已經其他程序佔有完了,即為0或者負數,那麼新程序要申請這個資源時資源數再減1必然count<0,那麼這個程序就要被被阻塞,進入阻塞佇列。
再看semSignal操作
一個程序終會使用完這個程序,然後離開,那麼此時可用資源數++
為什麼這個條件設定成<=0呢?一個程序用完資源走了,count++,如果還有程序在排隊(count即值是-1或者更小),那++之後必然count<=0,此時就喚醒一個排隊中的程序。
PV操作的意義:我們用訊號量及PV操作來實現程序的同步和互斥。PV操作屬於程序的低階通訊。
使用PV操作實現程序互斥時應該注意的是:
(1)每個程式中使用者實現互斥的P、V操作必須成對出現,先做P操作,進臨界區,後做V操作,出臨界區。若有多個分支,要認真檢查其成對性。
(2)P、V操作應分別緊靠臨界區的頭尾部,臨界區的程式碼應儘可能短,不能有死迴圈。
(3)互斥訊號量的初值一般為1。
三、總結
semWait(S):請求分配一個資源。
semSignal(S):釋放一個資源。
semWait、semSignal操作必須成對出現。
多個semWait操作的次序不能顛倒,否則可能導致死鎖。
多個semSignal操作的次序可任意。
用於互斥時,位於同一程序內;用於同步時,交錯出現於兩個合作程序內。且在前事件後加semSignal,在後事件前加semWait,比如先刷牙再吃飯,那刷牙這個事件後加semSignal,在吃飯這個事件前加semWait
互斥關係:緩衝區是臨界資源,各程序互斥訪問