1. 程式人生 > >作業系統的訊號量和管程

作業系統的訊號量和管程

訊號量 semaphore
訊號量是作業系統提供的一種協調共享資源訪問的方法
軟體同步是平等執行緒間的一種同步協商機制
OS是管理者,地位高於程序
用訊號量表示系統資源的數量

由一個整形 (sem)變數和兩個原子操作組成
P()(Prolaag (荷蘭語嘗試減少))
sem減1
如sem<0, 進入等待, 否則繼續
V()(Verhoog (荷蘭語增加))
sem加1
如sem≤0,喚醒一個等待程序

P() 可能阻塞,V()不會阻塞,這些只能在核心實現,而不能在使用者實現。

訊號量的使用:
(1) 同步 執行緒間的事件等待
(2) 互斥 臨界區的互斥訪問控制
用訊號量實現臨界區的互斥訪問必須成對使用P,V操作 ,不能次序錯誤、重複或者遺漏

條件同步:初值設為0

自旋鎖不能實現先進先出:
它是為實現保護共享資源而提出一種鎖機制。其實,自旋鎖與互斥鎖比較類似,它們都是為了解決對某項資源的互斥使用。無論是互斥鎖,還是自旋鎖,在任何時刻,最多隻能有一個保持者,也就說,在任何時刻最多隻能有一個執行單元獲得鎖。但是兩者在排程機制上略有不同。對於互斥鎖,如果資源已經被佔用,資源申請者只能進入睡眠狀態。但是自旋鎖不會引起呼叫者睡眠,如果自旋鎖已經被別的執行單元保持,呼叫者就一直迴圈在那裡看是否該自旋鎖的保持者已經釋放了鎖,”自旋”一詞就是因此而得名。
跟互斥鎖一樣,一個執行單元要想訪問被自旋鎖保護的共享資源,必須先得到鎖,在訪問完共享資源後,必須釋放鎖。如果在獲取自旋鎖時,沒有任何執行單元保持該鎖,那麼將立即得到鎖;如果在獲取自旋鎖時鎖已經有保持者,那麼獲取鎖操作將自旋在那裡,直到該自旋鎖的保持者釋放了鎖。由此我們可以看出,自旋鎖是一種比較低階的保護資料結構或程式碼片段的原始方式,這種鎖可能存在兩個問題:
死鎖。試圖遞迴地獲得自旋鎖必然會引起死鎖:遞迴程式的持有例項在第二個例項迴圈,以試圖獲得相同自旋鎖時,不會釋放此自旋鎖。在遞迴程式中使用自旋鎖應遵守下列策略:遞迴程式決不能在持有自旋鎖時呼叫它自己,也決不能在遞迴呼叫時試圖獲得相同的自旋鎖。此外如果一個程序已經將資源鎖定,那麼,即使其它申請這個資源的程序不停地瘋狂“自旋”,也無法獲得資源,從而進入死迴圈。
過多佔用cpu資源。如果不加限制,由於申請者一直在迴圈等待,因此自旋鎖在鎖定的時候,如果不成功,不會睡眠,會持續的嘗試,單cpu的時候自旋鎖會讓其它process動不了. 因此,一般自旋鎖實現會有一個引數限定最多持續嘗試次數. 超出後, 自旋鎖放棄當前time slice. 等下一次機會
由此可見,自旋鎖比較適用於鎖使用者保持鎖時間比較短的情況。正是由於自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠高於互斥鎖。訊號量和讀寫訊號量適合於保持時間較長的情況,它們會導致呼叫者睡眠,因此只能在程序上下文使用,而自旋鎖適合於保持時間非常短的情況,它可以在任何上下文使用。如果被保護的共享資源只在程序上下文訪問,使用訊號量保護該共享資源非常合適,如果對共享資源的訪問時間非常短,自旋鎖也可以。但是如果被保護的共享資源需要在中斷上下文訪問(包括底半部即中斷處理控制代碼和頂半部即軟中斷),就必須使用自旋鎖。自旋鎖保持期間是搶佔失效的,而訊號量和讀寫訊號量保持期間是可以被搶佔的。自旋鎖只有在核心可搶佔或SMP(多處理器)的情況下才真正需要,在單CPU且不可搶佔的核心下,自旋鎖的所有操作都是空操作,

與前面的區別 P,V程式碼的原子性由作業系統保護。

必須成對使用P,V
如果不申請(P操作),直接V操作:就會有多個執行緒進入到臨界區
如果申請了(P不操作),不釋放(V):誰都不能進入臨界區

不能避免死鎖的出現,必須在寫程式的時候解決這個問題。
sim++:(保證操作正確性)
TestAndSet(&lock),等價exchange
中斷
軟體處理
* 實現條件同步:執行緒A線上程B後執行
訊號量初始值:0
訊號量解決生產者,消費者問題
emptyBuffers->P()
mutex->P(); 兩者交換,程式出現死鎖

mutex-V()
fullBuffers->V()兩者交換順序,對程式正確性沒有任何影響。

第二種同步方法:管程
訊號量:P,V 操作分配在生產者與消費者中,P,V的配對是比較困難的。
訊號量使用鎖,管程使用條件變數,在解決同步互斥問題上,訊號量與管程等價。
管程:P,V操作的配對,集中在一起
(1)管程採用面向物件的方法,簡化了執行緒間的同步控制。
(2)任何時刻只有一個執行緒在執行管程程式碼
(3)臨界區也是任何時刻只有一個執行緒在執行管程程式碼,但是他們有區別,區別是:正在管程中的執行緒可臨時放棄管程的互斥訪問,等待事件出現時恢復。

管程的使用:
在物件、模組中收集相關共享的資料
定義訪問共享資料的方法,在其他地方不需要同步互斥操作了。
管程也可能出現死鎖的情況
管程中的區域性變數不可以被外部直接訪問
管成組成:
一個鎖(入口,出口)
控制管程程式碼的互斥訪問
0或者多個條件變數
管理共享資料的併發訪問
臨界區只有一個條件變數,而管程管理一系列的條件變數

條件變數
條件變數是管程內的等待機制
進入管程的執行緒因資源被佔用而進入等待狀態
每個條件變量表示一種等待原因,對應一個等待佇列
Wait()操作
將自己阻塞在等待佇列中
喚醒一個等待者或釋放管程的互斥訪問
與訊號量區別:不用判斷sim/numWaiting是否為空,直接加入waitQueue 中,而且不用管配對的問題。
Signal()操作
將等待佇列中的一個執行緒喚醒
如果等待佇列為空,則等同空操作
用管程解決生產者-消費者問題
效率不如訊號量的效率

讀者、寫者問題:
規則:“讀-讀“允許 “寫-讀”互斥” ‘ “寫-寫”互斥

管程實現讀者-寫者問題
AR=0 Active readers 當前正在讀的讀者
AW=0 當前正在寫的寫者
WR=0 當前等待的讀者
WW=0 當前等待的寫者

讀者-寫者問題 (考試實現讀者優先),
讀者優先
寫者端: StrarWrite() while(while(AR+WR>0)){okToWrite.wait()}
DoneWrite() if(AW==0&&WR>0){okToRead.signal()}

讀者端:
StartRead() while(AW+AR>0){okToRead.wait()}
DoneRead() if(WR>0){okToRead.signal()}else if(WW>0){okToWrite.signal()}

寫者優先
讀者端:
stratRead()函式:while(AW+WW>0){ okToReda.wait()}
DoneRead()函式: if(AR==0&&WW>0) //沒有讀者且有寫者等著則釋放鎖
oktowrite.signal()

寫者端:
startWrite() while((AW+AR)>0){ okToWrite.wait} 但是有等待讀的讀者,是不等的,則說明是寫者優先的
DoneWrite if(WW>0){okToWrite.signal()}else if(WR>0){okToRead.broadcast()}