一、概述
信號量是操作系統提供的一種協調共享資源訪問的方法。和用軟件實現的同步比較,軟件同步是平等線程間的的一種同步協商機制,不能保證原子性。而信號量則由操作系統進行管理,地位高於進程,操作系統保證信號量的原子性。
信號量是跟鎖機制在同一個層次上的編程方法。
管程是為了解決信號量在臨界區的PV操作上的配對的麻煩,把配對的PV操作集中在一起,生成的一種並發編程方法。其中使用了條件變量這種同步機制。

二、信號量
1)概述
信號中包括一個整形變量,和兩個原子操作P和V,其原子性由操作系統保證,這個整形變量只能通過P操作和V操作改變。
P意味着信號量值減1,減完之后如果信號量值小於0,則說明資源不夠用的,把進程加入等待隊列。
V意味着信號量值加1,加完之后如果信號量值小於等於0,則說明等待隊列里有進程,那么喚醒一個等待進程。


信號量的等待進程被放在等待隊列中,按先進先出的次序執行。
自旋鎖不能保證進程按先進先出的次序執行,因為自旋鎖的所有等待進程都在循環中忙等待,在臨界區釋放的那一刻,先檢查到臨界區為空的進程先進入臨界區,沒有先來后到之分。
2)具體實現:

3)信號量分類:
i)二進制信號量:資源數目為0或1
ii)資源信號量:資源數目為任何非負值
兩者其實是等價的,基於一個可以實現另一個。
4) 信號量作用:
i)實現臨界區的互斥訪問
進入臨界區之前使用P操作,如果信號量為1,則進入,且信號量設置為0。如果信號量為0,則進入等待隊列。
退出臨界區之后使用V操作,信號量值加1,如果信號量還小於等於0,則喚醒等待隊列中的一個進程。

ii)實現條件同步
一個線程A使用P操作,一個線程B使用V操作,初始的信號量設置為0。則為了滿足某個條件,必須在線程B執行之后,才可以執行線程A。用信號量可以輕松實現這一點。

5)用信號量實現生產者-消費者問題


6)使用信號量的缺陷
讀/開發代碼比較困難,而且PV在不同的線程里配對,容易寫錯。而且必須先檢查資源信號量的值,再進入臨界區(即先寫emptyBuffers->P(),再寫mutex->P()),否則所有線程都不能進入臨界區。
三、管程
1)概述
管程是為了解決信號量在臨界區的PV操作上的配對的麻煩,把配對的PV操作集中在一起,生成的一種並發編程方法。其中使用了條件變量這種同步機制。
管程與臨界區不同的是,在管程中的線程可以臨時放棄管程的互斥訪問,讓其他線程進入到管程中來。而臨界區中的線程只能在線程退出臨界區時,才可以放棄對臨界區的訪問。

2)管程的組成
當條件變量的數目為0時,管程和臨界區相同。

3)條件變量
一個條件變量就對應於一個等待隊列,每個條件變量有一個Wait()操作和Signal()操作。


4)用管程實現生產者-消費者問題

先進入管程,再進行判斷。就是因為管程中的線程可以放棄對管程的互斥訪問,交由其他線程訪問管程。
(視頻里說 管程可以把PV操作都集中到一個模塊里,我對此有疑問。。在我看來,不就是把兩個信號量表示成兩個條件變量了嘛。。PV還是分散的啊。求指教)
5)管程條件變量的釋放處理方式
進程A執行,被阻塞,進入等待隊列,切換到進程B,當進程B執行signal操作使進程A可以執行時,有兩種方式:一種是等進程B執行完再執行進程A,另一種是立即切換到進程A,等執行完進程A再切換到進程B,執行進程B。
第二種比第一種多了一次切換,所以第一種比較高效,但第二種容易證明其正確性。第一種主要用於真實OS和Java中,第二種主要見於教材中。

四、哲學家就餐問題

方案1:都先拿左邊的叉子,再拿右邊的叉子,拿不到就等着。當5個哲學家同時拿起左邊的叉子時,就會引起死鎖。

方案2:某一時間內,只能有一個哲學家在進餐,其他的人都不許拿叉子。結果正確,但是效率低。

方案3:按某一規律(下圖是按奇偶數分類)讓不同的哲學家先拿不同方向的叉子,即不是都先拿左手的叉子或右手的叉子,而是有的先拿左手的叉子,有的先拿右手的叉子。從而避免方案1中死鎖的發生。

五、讀者寫者問題







