Linux處理序間通訊(IPC)編程實踐(十)System V訊號量---PV操作經典題目

來源:互聯網
上載者:User

Linux處理序間通訊(IPC)編程實踐(十)System V訊號量---PV操作經典題目

//P原語//P(semaphore *S)
wait(semaphore *S){
-- S->value;if (S->value < 0)
{//將當前進程設定為阻塞狀態
//將當前進程的PCB插入相應的阻塞隊列S->list末尾block(S->list);
}} [

//V原語//V(semaphore *S)
signal(semaphore *S){
++ S->value;if (S->value <= 0) //表示有進程處於阻塞狀態
{//喚醒阻塞隊列S->list中等待的一個進程,將其置為就緒態;
//將其插入就緒隊列;wakeup (S->list);
}} p操作(wait):申請一個單位資源,進程進入

 

v操作(signal):釋放一個單位資源,進程出來

使用PV操作實現進程互斥時應該注意的是:

(1)每個程式中使用者實現互斥的P、V操作必須成對出現,先做P操作,進臨界區,後做V操作,出臨界區。若有多個分支,要認真檢查其成對性。

(2)P、V操作應分別緊靠臨界區的頭尾部,臨界區的代碼應儘可能短,不能有死迴圈。

(3)互斥訊號量的初值一般為1。

在接下來的經典題目的分析中,我們可以 將P操作看作是判斷型操作,也就是if;將V操作看作是通知型的操作,這樣更容易寫虛擬碼。

(一)生產者消費者問題

生產者一消費者問題(producer-consumerproblem)是指若干進程通過有限的共用緩衝區交換資料時的緩衝區資源使用問題。假設“生產者”進程不斷向共用緩衝區寫人資料(即生產資料),而“消費者”進程不斷從共用緩衝區讀出資料(即消費資料);共用緩衝區共有n個;任何時刻只能有一個進程可對共用緩衝區進行操作。所有生產者和消費者之間要協調,以完成對共用緩衝區的操作。

生產者進程結構:do{
wait(empty) ;wait(mutex) ;
 add nextp to buffer
 signal(mutex) ;
signal(full) ;}while(1) ;
 消費者進程結構:
do{wait(full) ;
wait(mutex) ;

remove an item from buffer to nextp

signal(mutex) ;signal(empty) ;
}while(1) ;

我們可把共用緩衝區中的n個緩衝塊視為共用資源,生產者寫人資料的緩衝塊成為消費者可用資源,而消費者讀出資料後的緩衝塊成為生產者的可用資源。為此,可設定三個訊號量:full、empty和mutex。其中:full表示有資料的緩衝塊數目,初值是0;empty表示空的緩衝塊數初值是n;mutex用於訪問緩衝區時的互斥,初值是1。實際上,full和empty間存在如下關係:full + empty = N 注意:這裡每個進程中各個P操作的次序是重要的。各進程必須先檢查自己對應的資源數在確信有可用資源後再申請對整個緩衝區的互斥操作;否則,先申請對整個緩衝區的互斥操後申請自己對應的緩衝塊資源,就可能死結。出現死結的條件是,申請到對整個緩衝區的互斥操作後,才發現自己對應的緩衝塊資源,這時已不可能放棄對整個緩衝區的佔用。如果採用AND訊號量集,相應的進入區和退出區都很簡單。如生產者的進入區為Swait(empty,mutex),退出區為Ssignal(full,mutex)。
 (二)讀者寫者問題 讀者—寫者問題(Readers-Writers problem)也是一個經典的並發程式設計問題,是經常出現的一種同步問題。電腦系統中的資料(檔案、記錄)常被多個進程共用,但其中某些進程可能只要求讀資料(稱為讀者Reader);另一些進程則要求修改資料(稱為寫者Writer)。就共用資料而言,Reader和Writer是兩組並發進程共用一組資料區,要求: (1)允許多個讀者同時執行讀操作; (2)不允許讀者、寫者同時操作; (3)不允許多個寫者同時操作。 我們使用讀者優先策略解決: int rc=0;  //用於記錄當前的讀者數量  semaphore rc_mutex=1;//用於對共用變數rc 操作的互斥訊號量  semaphore write=1; //用於保證讀者和寫者互斥地訪問的訊號量   void reader()do{  P(rc_mutex); //開始對rc共用變數進行互斥訪問    rc ++; //來了一個讀進程,讀進程數加1  if (rc==1)  P(write);//如是第一個讀進程,判斷是否有寫進程在臨界區,  //若有,讀進程等待,若無,阻塞寫進程  V(rc_mutex); //結束對rc共用變數的互斥訪問  讀檔案;  P(rc_mutex); //開始對rc共用變數的互斥訪問    rc--;  //一個讀進程讀完,讀進程數減1  if (rc == 0)  V(write);  //最後一個離開臨界區的讀進程需要判斷是否有寫進程//需要進入臨界區,若有,喚醒一個寫進程進臨界區  V(rc_mutex);//結束對rc共用變數的互斥訪問  } while(1)   void writer() do{  P(write); //無讀進程,進入寫進程;若有讀進程,寫進程等待  寫檔案;  V(write);//寫進程完成;判斷是否有讀進程需要進入臨界區,  //若有,喚醒一個讀進程進臨界區   } while(1)   讀者優先的設計思想是讀進程只要看到有其它讀進程正在讀,就可以繼續進行讀;寫進程必須等待所有讀進程都不讀時才能寫,即使寫進程可能比一些讀進程更早提出申請。該演算法只要還有一個讀者在活動,就允許後續的讀者進來,該策略的結果是,如果有一個穩定的讀者流存在,那麼這些讀者將在到達後被允許進入。而寫者就始終被掛起,直到沒有讀者為止。 (三)哲學家就餐問題 假設有五位哲學家圍坐在一張圓形餐桌旁,做以下兩件事情之一:吃飯,或者思考。吃東西的時候,他們就停止思考,思考的時候也停止吃東西。每兩個哲學家之間有一隻餐叉。因為用一隻餐叉很難吃飯,所以假設哲學家必須用兩隻餐叉吃東西, 而且他們只能使用自己左右手邊的那兩隻餐叉。[cpp] view plaincopyvoid philosopher(int i) /*i:哲學家編號,從0 到4*/{ while (TRUE) {  think( ); /*哲學家正在思考*/  take_fork(i); /*取左側的筷子*/  take_fork((i+1) % N); /*取左側筷子;%為模數運算*/  eat( ); /*吃飯*/  put_fork(i); /*把左側筷子放回桌子*/  put_fork((i+1) % N); /*把右側筷子放回桌子*/ }} 分析:假如所有的哲學家都同時拿起左側筷子,看到右側筷子不可用,又都放下左側筷子, 等一會兒,又同時拿起左側筷子,如此這般,永遠重複。對於這種情況,即所有的程式都在 無限期地運行,但是都無法取得任何進展,即出現饑餓,所有哲學家都吃不上飯。  問題演算法描述:  規定在拿到左側的筷子後,先檢查右面的筷子是否可用。如果不可用,則先放下左側筷子, 等一段時間再重複整個過程。  分析:當出現以下情形,在某一個瞬間,所有的哲學家都同時啟動這個演算法,拿起左側的筷子,而看到右側筷子不可用,又都放下左側筷子,等一會兒,又同時拿起左側筷子……如此這樣永遠重複下去。對於這種情況,所有的程式都在運行,但卻無法取得進展,即出現饑餓,所有的哲學家都吃不上飯。  2) 描述一種沒有人餓死(永遠拿不到筷子)演算法。  考慮了四種實現的方式(A、B、C、D):  A: 原理:至多隻允許四個哲學家同時進餐,以保證至少有一個哲學家能夠進餐,最終總會釋放出他所使用過的兩支筷子,從而可使更多的哲學家進餐。以下將room 作為訊號量,只允許4 個哲學家同時進入餐廳就餐,這樣就能保證至少有一個哲學家可以就餐,而申請進入餐廳的哲學家進入room 的等待隊列,根據FIFO 的原則,總會進入到餐廳就餐,因此不會出現餓死和死結的現象。  偽碼: [cpp] view plaincopysemaphore chopstick[5]={1,1,1,1,1};  semaphore room=4;void philosopher(int i){ while(true) {  think();  wait(room); //請求進入房間進餐  wait(chopstick[i]); //請求左手邊的筷子  wait(chopstick[(i+1)%5]); //請求右手邊的筷子  eat();  signal(chopstick[(i+1)%5]); //釋放右手邊的筷子  signal(chopstick[i]); //釋放左手邊的筷子  signal(room); //退出房間釋放訊號量room }} B: 原理:僅當哲學家的左右兩支筷子都可用時,才允許他拿起筷子進餐。 方法1:利用AND 型訊號量機制實現:根據課程講述,在一個原語中,將一段代碼同時需要的多個臨界資源,要麼全部分配給它,要麼一個都不分配,因此不會出現死結的情形。當某些資源不夠時阻塞調用進程;由於等待隊列的存在,使得對資源的請求滿足FIFO 的要求,因此不會出現饑餓的情形。 偽碼:semaphore chopstick[5]={1,1,1,1,1};void philosopher(int I){ while(true) {  think();  Swait(chopstick[(I+1)]%5,chopstick[I]);  eat();  Ssignal(chopstick[(I+1)]%5,chopstick[I]); }}  方法2:利用訊號量的保護機制實現。通過訊號量mutex對eat()之前的取左側和右側筷子的操作進行保護,使之成為一個原子操作,這樣可以防止死結的出現。偽碼:semaphore mutex = 1 ;semaphore chopstick[5]={1,1,1,1,1};void philosopher(int I){ while(true) {  think();  wait(mutex);  wait(chopstick[(I+1)]%5);  wait(chopstick[I]);  signal(mutex);  eat();  signal(chopstick[(I+1)]%5);  signal(chopstick[I]); }}   C: 原理:規定奇數號的哲學家先拿起他左邊的筷子,然後再去拿他右邊的筷子;而偶數號的哲學家則相反.按此規定,將是1,2號哲學家競爭1號筷子,3,4號哲學家競爭3號筷子.即五個哲學家都競爭奇數號筷子,獲得後,再去競爭偶數號筷子,最後總會有一個哲學家能獲得兩支筷子而進餐。而申請不到的哲學家進入阻塞等待隊列,根FIFO原則,則先申請的哲學家會較先可以吃飯,因此不會出現餓死的哲學家。 偽碼:semaphore chopstick[5]={1,1,1,1,1};void philosopher(int i){ while(true) {  think();  if(i%2 == 0) //偶數哲學家,先右後左。  {wait (chopstick[ i + 1 ] mod 5) ;wait (chopstick[ i]) ;eat();signal (chopstick[ i + 1 ] mod 5) ;signal (chopstick[ i]) ;  }  Else //奇數哲學家,先左後右。  {wait (chopstick[ i]) ;wait (chopstick[ i + 1 ] mod 5) ;eat();signal (chopstick[ i]) ;signal (chopstick[ i + 1 ] mod 5) ;  } }}   D: 利用管程機制實現(最終該實現是失敗的,見以下分析):   原理:不是對每隻筷子設定訊號量,而是對每個哲學家設定訊號量。test()函數有以下作用:   a. 如果當前處理的哲學家處於饑餓狀態且兩側哲學家不在吃飯狀態,則當前哲學家通過test()函數試圖進入吃飯狀態。 b. 如果通過test()進入吃飯狀態不成功,那麼當前哲學家就在該訊號量阻塞等待,直到其他的哲學家進程通過test()將該哲學家的狀態設定為EATING。 c. 當一個哲學家進程調用put_forks()放下筷子的時候,會通過test()測試它的鄰居,如果鄰居處於饑餓狀態,且該鄰居的鄰居不在吃飯狀態,則該鄰居進入吃飯狀態。   由上所述,該演算法不會出現死結,因為一個哲學家只有在兩個鄰座都不在進餐時,才允許轉換到進餐狀態。   該演算法會出現某個哲學家適終無法吃飯的情況,即當該哲學家的左右兩個哲學家交替處在吃飯的狀態的時候,則該哲學家始終無法進入吃飯的狀態,因此不滿足題目的要求。 但是該演算法能夠實現對於任意多位哲學家的情況都能獲得最大的並行度,因此具有重要的意義。偽碼:#define N 5 /* 哲學家人數*/#define LEFT (i-1+N)%N /* i的左鄰號碼 */#define RIGHT (i+1)%N /* i的右鄰號碼 */typedef enum { THINKING, HUNGRY, EATING } phil_state; /*哲學家狀態*/monitor dp /*管程*/{ phil_state state[N]; semaphore mutex =1; semaphore s[N]; /*每個哲學家一個訊號量,初始值為0*/ void test(int i) {  if ( state[i] == HUNGRY &&state[LEFT(i)] != EATING && state[RIGHT(i)] != EATING )  {state[i] = EATING;V(s[i]);  } } void get_forks(int i) {  P(mutex);  state[i] = HUNGRY;  test(i); /*試圖得到兩支筷子*/  V(mutex);  P(s[i]); /*得不到筷子則阻塞*/ } void put_forks(int i) {  P(mutex);  state[i]= THINKING;  test(LEFT(i)); /*看左鄰是否進餐*/  test(RIGHT(i)); /*看右鄰是否進餐*/  V(mutex); }}    哲學家進程如下:void philosopher(int process)  { while(true) {  think();  get_forks(process);  eat();  put_forks(process); }}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.