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


    現代操作系統采用多道程序設計機制,多個進程可以並發執行,CPU在進程之間來回切換,共享某些資源,提高了資源的利用率,但這也使得處理並發執行的多個進程之間的沖突和相互制約關系成為了一道難題。如果對並發進程的調度不當,則可能會出現運行結果與切換時間有關的情況,令結果不可再現,影響系統的效率和正確性,嚴重時還會使系統直接崩潰。就比如你只有一台打印機,有兩個進程都需要打印文件,如果直接讓他們簡單地並發訪問打印機,那么你很可能什么都打印不出來或者打印的文件是...anyway,我們需要增加一些機制來控制並發進程間的這種相互制約關系。

    進程間通信的很多問題的根本原因是我們不知道進程何時切換。

   概念

    首先我們了解一下臨界資源與臨界區的概念:臨界資源就是一次只允許一個進程訪問的資源,一個進程在使用臨界資源的時候,另一個進程是無法訪問的,操作系統也不能夠中途剝奪正在使用者的使用權利,正所謂“潑出去的女兒嫁出去的水”是也。即臨界資源是不可剝奪性資源。那么臨界區呢?所謂臨界區就是進程中范文臨界資源的那段程序代碼,注意,是程序代碼,不是內存資源了,這就是臨界資源與臨界區的區別。我們規定臨界區的使用原則(也即同步機制應遵循的准則)十六字訣:“空閑讓進,忙則等待,有限等待,讓權等待”--strling。讓我們分別來解釋一下:

(1)空閑讓進:臨界資源空閑時一定要讓進程進入,不發生“互斥禮讓”行為。

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

(3)有限等待:進程等待進入臨界區的時間是有限的,不會發生“餓死”的情況。

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

    好了,我們進入下一部分。

    進程間通常存在着兩種制約關系:直接制約關系和間接制約關系,就是我們通常所說的進程的同步與互斥。顧名思義,一個是合作關系,一個是互斥關系。進程互斥說白了就是“你用的時候別人都不能用,別人用的時候,你也不能去用”,是一種源於資源共享的間接制約關系。進程同步指的是“我們大家利用一些共同的資源區,大家一起合作,完成某些事情,但是我在干某些小事的時候,可能要等到你做完另一些小事”,是一種源於相互合作的直接制約關系。兩者區別在於互斥的進程間沒有必然的聯系,屬於競爭者關系,誰競爭到資源(的使用權),誰就使用它,直到使用完才歸還。就比如洗衣房的洗衣機這個資源,去洗衣的同學並不需要有必然聯系,你們可以互不認識,但是誰競爭到洗衣機的使用權,就可以使用,直到洗完走人。而同步的進程間是有必然聯系的,即使競爭到使用權,如果合作者沒有發出必要的信息,該進程依然不能執行。就比如排隊打水,即使排到你了,如果水箱沒水了,你就打不了水,說明你和水箱是有着必然聯系的,你得從它里面取水,你們是同步關系,你們合作完成“打水”這個過程。

    那么先來討論如何實現進程的互斥控制。有下列幾種方法:嚴格輪換(每個進程每次都從頭執行到尾,效率不高,可能等待很久),屏蔽中斷(剛剛進入臨界區時就屏蔽中斷,剛要出臨界區就打開中斷),專用機器指令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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM