用信號量解決進程的同步與互斥探討【持續更新】

    現代操做系統採用多道程序設計機制,多個進程能夠併發執行,CPU在進程之間來回切換,共享某些資源,提升了資源的利用率,但這也使得處理併發執行的多個進程之間的衝突和相互制約關係成爲了一道難題。若是對併發進程的調度不當,則可能會出現運行結果與切換時間有關的狀況,令結果不可再現,影響系統的效率和正確性,嚴重時還會使系統直接崩潰。就好比你只有一臺打印機,有兩個進程都須要打印文件,若是直接讓他們簡單地併發訪問打印機,那麼你極可能什麼都打印不出來或者打印的文件是...anyway,咱們須要增長一些機制來控制併發進程間的這種相互制約關係。html

    進程間通訊的不少問題的根本緣由是咱們不知道進程什麼時候切換。算法

   概念

    首先咱們瞭解一下臨界資源與臨界區的概念:臨界資源就是一次只容許一個進程訪問的資源,一個進程在使用臨界資源的時候,另外一個進程是沒法訪問的,操做系統也不可以中途剝奪正在使用者的使用權利,正所謂「潑出去的女兒嫁出去的水」是也。即臨界資源是不可剝奪性資源。那麼臨界區呢?所謂臨界區就是進程中範文臨界資源的那段程序代碼,注意,是程序代碼,不是內存資源了,這就是臨界資源與臨界區的區別。咱們規定臨界區的使用原則(也即同步機制應遵循的準則)十六字訣:「空閒讓進,忙則等待,有限等待,讓權等待」--strling。讓咱們分別來解釋一下:數據庫

(1)空閒讓進:臨界資源空閒時必定要讓進程進入,不發生「互斥禮讓」行爲。數組

(2)忙則等待:臨界資源正在使用時外面的進程等待。網絡

(3)有限等待:進程等待進入臨界區的時間是有限的,不會發生「餓死」的狀況。數據結構

(4)讓權等待:進程等待進入臨界區是應該放棄CPU的使用。併發

    好了,咱們進入下一部分。app

    進程間一般存在着兩種制約關係:直接制約關係和間接制約關係,就是咱們一般所說的進程的同步與互斥。顧名思義,一個是合做關係,一個是互斥關係。進程互斥說白了就是「你用的時候別人都不能用,別人用的時候,你也不能去用」,是一種源於資源共享的間接制約關係。進程同步指的是「咱們你們利用一些共同的資源區,你們一塊兒合做,完成某些事情,可是我在幹某些小事的時候,可能要等到你作完另外一些小事」,是一種源於相互合做的直接制約關係。二者區別在於互斥的進程間沒有必然的聯繫,屬於競爭者關係,誰競爭到資源(的使用權),誰就使用它,直到使用完才歸還。就好比洗衣房的洗衣機這個資源,去洗衣的同窗並不須要有必然聯繫,大家能夠互不認識,可是誰競爭到洗衣機的使用權,就可使用,直到洗完走人。而同步的進程間是有必然聯繫的,即便競爭到使用權,若是合做者沒有發出必要的信息,該進程依然不能執行。就好比排隊打水,即便排到你了,若是水箱沒水了,你就打不了水,說明你和水箱是有着必然聯繫的,你得從它裏面取水,大家是同步關係,大家合做完成「打水」這個過程。ide

    那麼先來討論如何實現進程的互斥控制。有下列幾種方法:嚴格輪換(每一個進程每次都從頭執行到尾,效率不高,可能等待好久),屏蔽中斷(剛剛進入臨界區時就屏蔽中斷,剛要出臨界區就打開中斷),專用機器指令test_and_set,test_and_clear,加鎖,軟件方法,信號量機制。講一下加鎖和軟件方法,加鎖方法以下:設置一個鎖標誌K表示臨界資源的狀態,K=1表示臨界資源正在被使用,K=0表示沒有進程在訪問臨界資源。若是一個進程須要訪問臨界資源,那麼先檢查鎖標誌K:測試

if K == 1, 循環檢測,直到K = 0

else if K == 0,設置鎖標誌爲1,進入臨界區

離開臨界區時設置鎖標誌K爲0. 軟件方法相似,如愛斯基摩人的小屋協議,愛斯基摩人的小屋很小,每次只能容納一我的進入,小屋內有一個黑板,上面標誌這可以進入臨界區的進程。若進程申請進入臨界區,則先進入小屋檢查黑板標誌,若是是本身,那麼離開小屋進入臨界區,執行完後進入小屋修改黑板標誌爲其餘進程,離開小屋。若是小屋黑板標誌不是本身,那麼反覆進入小屋考察黑板標誌是否是本身。這兩種方法都實現了互斥訪問,可是都違反了四條原則之一:讓權等待,都須要不斷的循環重複檢測標誌,霸佔了CPU資源,不是很好的方法。

    到後來,荷蘭計算機科學家Dijkstra於1965年提出瞭解決進程同步與互斥問題的信號量機制,收到了很好的效果,被一直沿用至今,普遍應用與單處理機和多處理機系統以及計算機網絡中。信號量機制就是說兩個或者多個進程經過他們均可以利用的一個或多個信號來實現準確無誤不衝突的併發執行。若是臨界資源不夠,就會有一個信號表示出來,若是進程此時想訪問,那麼就會阻塞到一個隊列中,等待調度。當臨界資源使用完畢,一個進程改變信號,並及時喚醒阻塞的進程,這就實現了進程間的同步和互斥問題。

    信號量分爲整型信號量,記錄型信號量,AND信號量以及信號量集。最初的信號量就是整型信號量,定義信號量爲一個整型變量,僅能經過兩個原子操做P,V來訪問,所謂原子操做就是指一組相聯的操做要麼不間斷地執行,要麼不執行。這兩個操做又稱爲wait和signal操做或者down和up操做。之因此叫P,V操做是由於Dijkstra是荷蘭人,P指的是荷蘭語中的「proberen」,意爲「測試」,而V指的是荷蘭語中的「verhogen」,意爲「增長」。最初P,V操做被描述爲:

P(S):   while (S≤0)  {do nothing};

        S=S-1;

V(S):   S=S+1;

可是這樣明顯違反了「讓權等待的原則」,後來發展爲記錄型信號量,記錄型信號量的數據結構是一個兩元組,包含信號量的值value和關於此信號量的阻塞隊列Q,value具備非負初值,通常反映了資源的數量,只能由P,V操做改變其值。(還有另外一種定義,信號量由value和P組成,value爲信號量的值,P爲指向PCB隊列的指針)。

記錄型信號量的P,V操做原語爲:

P(S):   S.value = S.value-1;
        if(S.value < 0)
           block(S,Q);

V(S):   S.value = S.value + 1;
        if(S.value <= 0)
            wakeup(S,Q);

    咱們來詳細解釋一下這兩個操做的含義:

    首先,P操做,首先將S.value減1,表示該進程須要一個臨界資源,若是S.value<0,那麼說明原來的S.value <= 0,即已經沒有資源可用了,因而將進程阻塞到與信號量S相關的阻塞隊列中去,若是S.value<0,那麼|S.value|其實就表示阻塞隊列的長度,即等待使用資源的進程數量。而後,V操做:首先S.value加1,表示釋放一個資源,若是S.value <= 0,那麼說明原來的S.value < 0,阻塞隊列中是由進程的,因而喚醒該隊列中的一個進程。那麼,爲何S.value > 0時不喚醒進程呢,很簡單,由於阻塞隊列中沒有進程了。

    P操做至關於「等待一個信號」,而V操做至關於「發送一個信號」,在實現同步過程當中,V操做至關於發送一個信號說合做者已經完成了某項任務,在實現互斥過程當中,V操做至關於發送一個信號說臨界資源可用了。實際上,在實現互斥時,P,V操做至關於申請資源和釋放資源。

    咱們將信號量初值設置爲1時一般可實現互斥,由於信號量表示資源可用數目,互斥信號量保證只有一個進程訪問臨界資源,至關於只有一個訪問權可用。設置爲0或者N時能夠用來實現同步。咱們後面將會在生產者-消費者問題中看到這點。用P,V操做實現互斥相似於加鎖的實現,在臨界區以前加P操做,在臨界區以後加V操做,便可互斥控制進程進入臨界區,訪問臨界資源。記錄型信號量因爲引入了阻塞機制,消除了不讓權等待的狀況,提升了實現的效率。

    經典問題

    下面經過一些實例詳細講解如何使用信號量機制解決進程同步與互斥問題。先說明一條規律,即:同步與互斥實現的P,V操做雖然都是成對出現,可是互斥的P,V操做出如今同一個進程的程序裏,而同步的P,V操做出如今不一樣進程的程序中。

問題1:生產者-消費者問題

    經典的同步互斥問題,也稱做「有界緩衝區問題」。具體表現爲:

1.兩個進程對同一個內存資源進行操做,一個是生產者,一個是消費者。

2.生產者往共享內存資源填充數據,若是區域滿,則等待消費者消費數據。

3.消費者從共享內存資源取數據,若是區域空,則等待生產者填充數據。

4.生產者的填充數據行爲和消費者的消費數據行爲不可在同一時間發生。

    生產者-消費者之間的同步關係表現爲緩衝區空,則消費者須要等待生產者往裏填充數據,緩衝區滿則生產者須要等待消費者消費。二者共同完成數據的轉移或傳送。生產者-消費者之間的互斥關係表現爲生產者往緩衝區裏填充數據的時候,消費者沒法進行消費,須要等待生產者完成工做,反之亦然。

既然瞭解了互斥與同步關係,那麼咱們就來設置信號量:

    因爲有互斥關係,因此咱們應該設置一個互斥量mutex控制二者不能同時操做緩衝區。此外,爲了控制同步關係,咱們設置兩個信號量empty和full來表示緩衝區的空槽數目和滿槽數目,即有數據的緩衝區單元的個數。mutex初值爲1,empty初值爲n,即緩衝區容量,表明初始沒有任何數據,有n個空的單元,相似的,full初值爲0.

    下面進行生產者-消費者行爲設計:

void Productor() {
    while(1) {
        //製造數據
        P(&empty);
        P(&mutex);
        //填充數據
        V(&mutex);
        V(&full);
    }
}

void Consumer() {
    while(1) {
        P(&full);
        P(&mutex);
        //消費數據
        V(&mutex);
        V(&empty);
    }
}

    這樣咱們的分析也就完成了,http://www.cnblogs.com/whatbeg/p/4419979.html 這篇文章裏有我用Windows API實現的用信號量實現生產者-消費者問題。

    下面,問題來了,咱們的生產者和消費者裏面都有兩個P,兩個V操做,那麼兩個P操做能否調換順序呢?V操做呢?想想。

    答案是P操做不可對換,V操做能夠。爲何呢?想象一下這種狀況,生產者執行P(mutex)把互斥量鎖住,而後再P(empty),此時empty < 0,鎖住,沒法繼續生產,等待消費者消費,消費者卻是也想消費,但是mutex被鎖住了啊,因而兩我的就等啊等,就成了等待戈多了。。可是V操做是能夠隨意調換的,由於V操做是解鎖和喚醒,不會由於它鎖住什麼。

問題2:讀者-寫者問題

第二個經典問題是讀者-寫着問題,它爲數據庫的訪問創建了一個模型。規則以下:

1.一個進程在讀的時候,其餘進程也能夠讀。

2.一個進程在讀/寫的時候,其餘進程不能進行寫/讀。

3.一個進程在寫的時候,其餘進程不能寫。

咱們來分析他們的關係,首先,這個問題沒有明顯的同步關係,由於在這個問題裏,讀和寫並不要合做完成某些事情。可是是有互斥關係的,寫者和寫者,寫者和讀者是有互斥關係的,咱們須要設置一個mutex來控制其訪問,可是單純一個信號量的話會出現讀者和讀者的互斥也出現了,由於咱們可能有多個讀者,因此咱們設置一個變量ReadCount表示讀者的數量,好,這個時候,對於ReadCount又要實現多個讀者對他的互斥訪問,因此還要設置一個RC_mutex。這樣就行了。而後是行爲設計:

void Reader() {
    while(1) {
        P(&RC_mutex);
        rc = rc + 1;
        if(rc == 1) P(&mutex);  //若是是第一個讀者,那麼限制寫者的訪問
        V(&RC_mutex);
        //讀數據
        P(&RC_mutex);
        rc = rc - 1;
        if(rc == 0) V(&mutex);  //若是是最後一個讀者,那麼釋放以供寫者或讀者訪問
        V(&RC_mutex);
    }
}

void Writer() {
    while(1) {
        P(&mutex);
        //寫數據
        V(&mutex);
    }
}

其實,這個方法是有必定問題的,只要趁前面的讀者還沒讀完的時候新一個讀者進來,這樣一直保持,那麼寫者會一直得不到機會,致使餓死。有一種解決方法就是在一個寫者到達時,若是後面還有新的讀者進來,那麼先掛起那些讀者,先執行寫者,可是這樣的話併發度和效率又會降到很低。有人提出了一種寫者優先的解法,有點很差理解,這裏給出實現:

//寫者優先的讀者-寫者問題解法

Semaphore x = y = z = 1;    //x控制ReadCount的互斥訪問,y控制WriteCount的互斥訪問
Semaphore rsem = wsem = 1;  //rsem,wsem分別表示對讀和寫的互斥控制
int ReadCount = WriteCount = 0;

void Reader() {
    P(z);                       //z保證寫跳過讀,作到寫優先
    P(rsem);                    //控制對讀的訪問,若是有寫者,那麼此處不成功
    P(x);                       //對RC的互斥控制
    ReadCount++;                
    if(ReadCount == 1) P(wsem); //第一個讀者出現後,鎖住不讓寫
    V(x);
    V(rsem);                    //釋放讀的訪問,以使其餘讀者進入
    V(z);
    //讀數據...
    P(x);
    ReadCount--;
    if(ReadCount == 0) V(wsem); //若是是最後一個讀者,釋放對寫的信號
    V(x);
}

void Writer() {
    P(y);
    WriteCount++;
    if(WriteCount == 1) P(rsem);
    V(y);
    P(wsem);
    //寫數據...
    V(wsem);
    P(y);
    WriteCount--;
    if(WriteCount == 0) V(rsem);
    V(y);
}

問題3:哲學家就餐問題

哲學家就餐問題描述以下:

有五個哲學家,他們的生活方式是交替地進行思考和進餐,哲學家們共用一張圓桌,分別坐在周圍的五張椅子上,在圓桌上有五個碗和五支筷子,平時哲學家進行思考,飢餓時便試圖取其左、右最靠近他的筷子,只有在他拿到兩支筷子時才能進餐,進餐完畢,放下筷子又繼續思考。

約束條件
(1)只有拿到兩隻筷子時,哲學家才能吃飯。
(2)若是筷子已被別人拿走,則必須等別人吃完以後才能拿到筷子。
(3)任一哲學家在本身未拿到兩隻筷子吃飯前,不會放下手中拿到的筷子。
(4)用完以後將筷子返回原處

分析:筷子是臨界資源,每次只被一個哲學家拿到,這是互斥關係。若是筷子被拿走,那麼須要等待,這是同步關係。

容易想到一種錯誤的解法,因此設置一個信號量表示一隻筷子,有5只筷子,因此設置5個信號量,哲學家每次飢餓時先試圖拿左邊的筷子,再試圖拿右邊的筷子,拿不到則等待,拿到了就進餐,最後逐個放下筷子。這種狀況可能會產生死鎖,由於咱們不知道進程什麼時候切換(這也是不少IPC問題的根本緣由),若是5個哲學家同時飢餓,同時試圖拿起左邊的筷子,也很幸運地都拿到了,那麼他們拿右邊的筷子的時候都會拿不到,而根據第三個約束條件,都不會放下筷子,這就產生了死鎖。《現代操做系統》中記載的一種解法是僅當一個哲學家左右的筷子均可用時,纔拿起筷子,將「試圖獲取兩個筷子」做爲臨界資源,用一個互斥量mutex實現對其的互斥控制,而後用n個變量記錄哲學家的狀態(飢餓,進餐,思考<無關緊要,由於除了前二者之外只會思考>),而後用一個同步信號量數組,每一個信號量對應一個哲學家,來保證哲學家得不到本身所需筷子的時候阻塞。算法以下:

 

還有一種解法是讓奇數號與偶數號的哲學家拿筷子的前後順序不一樣,以破壞環路等待條件。還能夠只容許4個哲學家同時進餐(4我的都拿起一隻筷子的時候,第5我的不能再拿筷子,這樣就會空出一隻筷子)

 

    例題分析

    至此,咱們已經能夠總結出一點用信號量解決同步互斥問題的基本規律和通常步驟:

    (1)分析各進程間的制約關係,從而得出同步與互斥關係

    (2)根據(1)中的分析,設置信號量

    (3)編寫僞代碼,實施P,V操做

    同步:多個進程在執行次序上的協調,相互等待消息

     互斥:對臨界資源的使用

 

    要注意的是,雖然P,V操做在每個進程中都是成對出現的,但不必定是針對一個信號量。互斥信號量的P,V操做老是出如今一個進程中的臨界區的先後,而同步信號量的P,V操做老是出如今具備同步關係的兩個進程中,須要等待消息的一方執行P操做,發出消息的一方執行V操做。

    下面經過諸多例題來熟悉,掌握及訓練用信號量解決同步與互斥問題的通常方法。

 

問題4:放水果問題

桌上有一空盤,最多容許存放一隻水果。爸爸可向盤中放一個蘋果,媽媽可向盤中放一個桔子。

兒子專等吃盤中的桔子,女兒專等吃蘋果。

試用P、V操做實現爸爸、媽媽、兒子、女兒四個併發進程的同步。

分析:臨界資源是盤子,放的時候不能取,取的時候不能放,取的時候不能再取。同步關係:爸爸、媽媽與盤子爲空,兒子與盤中有桔,女兒與盤中有蘋果。

因此設置一個mutex互斥信號量來控制對盤子的訪問,用empty,orange,apple分別表明以上同步關係。程序以下:

Semaphore mutex = 1;
Semaphore empty = 1, orange = apple = 0;

mother:
    while(1) {
        P(empty);
        P(mutex);
        //放入桔子
        V(mutex)
        V(orange);
    }

father:
    while(1) {
        P(empty);
        P(mutex);
        //放入蘋果
        V(mutex)
        V(apple);
    }

son:
    while(1) {
        P(orange)
        P(mutex)
        //取桔子
        V(mutex);
        V(empty);
    }
    
daughter:
    while(1) {
        P(apple)
        P(mutex)
        //取蘋果
        V(mutex);
        V(empty);
    }
View Code

 

問題5:讀文件問題

四個進程A、B、C、D都要讀一個共享文件F,系統容許多個進程同時讀文件F。但限制是進程A和進程C不能同時讀文件F,進程B和進程D也不能同時讀文件F。爲了使這四個進程併發執行時能按系統要求使用文件,現用P、V操做進行管理。

分析:互斥關係:A和C讀文件時互斥,B和D讀文件時互斥,沒有同步關係。

因此設置兩個互斥信號量:AC_mutex,BD_mutex便可。僞代碼以下:

Semaphore AC_mutex = BD_mutex = 1;

A:
    while(1) {
        P(AC_mutex);
        //read F
        V(AC_mutex);
    }
B:
    while(1) {
        P(BD_mutex);
        //read F
        V(BD_mutex);
    }
C:
    while(1) {
        P(AC_mutex);
        //read F
        V(AC_mutex);
    }
D:
    while(1) {
        P(BD_mutex);
        //read F
        V(BD_mutex);
    }
View Code

 

問題6:閱覽室問題 / 圖書館問題

有一閱覽室,讀者進入時必須先在一張登記表上進行登記,該表爲每一座位列一表目,包括座號和讀者姓名。讀者離開時要消掉登記信號
,閱覽室中共有100個座位。用PV操做控制這個過程。

分析:

因爲每一個讀者都會進行同樣的操做:登記->進入->閱讀->撤銷登記->離開,因此創建一個讀者模型便可。

臨界資源有:座位,登記表

讀者間有座位和登記表的互斥關係,因此設信號量empty表示空座位的數量,初始爲100,mutex表示對登記表的互斥訪問,初始爲1。

P,V操做以下:

Semaphore mutex = 1, empty = 100;
Reader():
While(true) {
    P(empty)           //申請空座位
    P(mutex)           //申請登記表
    //登記  
    V(mutex)           //釋放登記表
    //進入閱讀
    P(mutex)            //申請登記表
    //撤銷登記
    V(mutex)            //釋放登記表
    V(empty)            //釋放座位
}
View Code

 

問題7:單行道問題

一段雙向行駛的公路,因爲山體滑坡,一小段路的通常車道被阻隔,該段每次只能容納一輛車經過,一個方向的多個車輛能夠緊接着經過,試用P,V操做控制此過程。

分析:

臨界資源爲一半被阻隔的一小段區域,因此須要Go_mutex,Come_mutex來控制每一個方向車輛經過該路段,以及實現兩個方向的同步關係,同步關係即爲:當某方向已有車輛在通行時,另外一方向的車輛必須等待,反之亦然。相似於讀者-寫者問題,車輛從兩邊經過至關於兩個讀者,咱們設立兩個計數器A和B分別表明兩個方向的汽車數量,還要設置兩個信號量A_mutex和B_mutex來實現對計數器的互斥訪問,由於山體滑坡處只容許一輛車經過,因此還需設置一個互斥量mutex保證相同方向的車輛依次經過該處。

因而程序以下(PV操做包含其中):

#include <Windows.h>
#include <stdio.h>
#define N 100
#define TRUE 1
typedef int Semaphore;
Semaphore A = 0, B = 0;
HANDLE Go_mutex,Come_mutex;
HANDLE A_mutex,B_mutex;
HANDLE mutex;

void down(HANDLE handle) {
    WaitForSingleObject(handle, INFINITE);
}

void up(HANDLE handle) {
    ReleaseSemaphore(handle, 1, NULL);
}

DWORD WINAPI Come(LPVOID v) {

    while(TRUE) {

        down(Come_mutex);

        down(A_mutex);
        A = A+1;
        if(A == 1) {
            down(Go_mutex);
            printf("                    <<<=====開始自東向西\n");
        }
        up(A_mutex);

        up(Come_mutex);

        down(mutex);
        //自東向西經過該路段
        printf("                    <<<=====第%s輛車\n",(char *)v);
        printf("         END        <<<=====第%s輛車\n",(char *)v);
        up(mutex);

        down(A_mutex);
        A = A-1;
        if(A == 0) {
            up(Go_mutex);
            printf("                    自東向西的全部車輛行駛完畢\n");
        }
        up(A_mutex);

        Sleep(2000);
    }
    return 1;
}

DWORD WINAPI Go(LPVOID v) {

    while(TRUE) {

        down(Go_mutex);

        down(B_mutex);
        B = B+1;
        if(B == 1) {
            down(Come_mutex);
            printf("開始自西向東====>\n");
        }
        up(B_mutex);

        up(Go_mutex);

        down(mutex);
        //自西向東經過該路段
        printf("第%s輛車=====>>>\n",(char *)v);
        printf("第%s輛車=====>>>     END\n",(char *)v);
        up(mutex);

        down(B_mutex);
        B = B-1;
        if(B == 0) {
            up(Come_mutex);
            printf("自西向東的全部車輛行駛完畢\n");
        }
        up(B_mutex);

        Sleep(2000);
    }
    return 1;
}

int main()
{
    DWORD Tid;
    char AThread[12][10];
    char BThread[12][10];

    mutex      = CreateSemaphore(NULL, 1, 1, NULL);
    A_mutex    = CreateSemaphore(NULL, 1, 1, NULL);
    B_mutex    = CreateSemaphore(NULL, 1, 1, NULL);
    Go_mutex   = CreateSemaphore(NULL, 1, 1, NULL);
    Come_mutex = CreateSemaphore(NULL, 1, 1, NULL);

    for(int i=0;i<4;i++) {
        AThread[i][0] = i+1+'0';
        AThread[i][1] = '\0';
        CreateThread(NULL,0,Come,AThread[i],0,&Tid);
    }

    for(int i=4;i<8;i++) {
        BThread[i][0] = i+1+'0';
        BThread[i][1] = '\0';
        CreateThread(NULL,0,Go,BThread[i],0,&Tid);
    }

    Sleep(20000);
    return 0;
}
View Code

運行結果:

 

從其中能夠看出,車輛正常交替順序經過該路段。數字重複出現是由於線程被重複地調度執行。

 

問題8:理髮師問題

理髮店理有一位理髮師、一把理髮椅和n把供等候理髮的顧客坐的椅子 若是沒有顧客,理髮師便在理髮椅上睡覺。 一個顧客到來時,它必
須叫醒理髮師 若是理髮師正在理髮時又有顧客來到,則若是有空椅子可坐,就坐下來等待,不然就離開。用PV操做管理該過程。

分析:

法1:首先設置一個count表示等待的人數(包括理髮椅上的那我的),初值爲0,以供後來者判斷是否應該離開。同時對count的訪問要保證互斥,因此設置mutex信號量來保證互斥,初值爲1。

臨界資源:凳子,理髮椅。 分別設置waitchair,barchair信號量,初值分別爲n和1,表示臨界資源數量。

同步關係:顧客和理髮師之間有同步關係,用ready和done信號量來表示,初值均爲0,ready表示顧客有沒有準備好,done表示理髮師是否完成一次理髮。

注意:並不是每個進程都須要while(1)無限循環,好比此例,顧客剪完一次頭髮就走了,不可能立刻再來剪,而之前的生產者-消費者不一樣,他們都是能夠不斷生產消費的。

寫出P,V操做以下:

Semaphore waitchair = n;
Semaphore barchair = 1;
Semaphore ready = done = 0;
int count = 0;
Semaphore mutex = 1;

barber:
    while(1) {
        P(ready);
        理髮
        V(done);
    }

consumer:
    P(mutex);
    if(count <= n) {
        count = count + 1;
        V(mutex);
    }
    else {
        V(mutex);
        離開
    }
    P(waitchair);
    P(barchair);
    V(waitchair);   //離開等待椅去理髮椅須要釋放等待椅!
    V(ready);       //準備好了
    P(done);        //等待理髮完成
    V(barchair);
    P(mutex);
    count = count - 1;
    V(mutex);
    離開
View Code

 

法2:將凳子和理髮椅看作同一種資源,由於只要理髮椅空就必定會有人湊上去,因此至關於每一個位置都是理髮椅,理髮師只須要去每一個有人的座位理髮便可。

仍是設置count表示正在理髮店中的人數,以便決定後來者是否離開。

同步關係仍用ready和done來表示。

算法:

Semaphore ready = done = 0;
int count = 0;
Semaphore mutex = 1;

barber:
    while(1) {
        P(ready);
        理髮
        V(done);
    }

consumer:
    P(mutex);
    if(count <= n) {
        count = count + 1;
        V(mutex);
    }
    else {
        V(mutex);
        離開
    }
    V(ready);       //準備好了
    P(done);        //等待理髮完成
    P(mutex);      //也可由理髮師來作count-1的操做
    count = count - 1;
    V(mutex);
    離開
View Code

 

 

好了,先說這麼多,例題會持續更新增長,感興趣的朋友能夠關注下。

鄙人學力有限,有不足或錯誤之處敬請指出,不勝感激。

 

參考文獻:

1.《現代操做系統》           --Andrew S. Tanenbaum

2.《操做系統設計與實現》 --Andrew S. Tanenbaum

3.《操做系統精髓與設計原理》  --Strling

4.《2015操做系統高分筆記》  --劉泱主編

 

更多精彩內容,歡迎關注公衆號:whatbegtalk

相關文章
相關標籤/搜索