1. 程式人生 > >PV原語詳解

PV原語詳解

    PV原語通過操作訊號量來處理程序間的同步與互斥的問題。其核心就是一段不可分割不可中斷的程式。 訊號量的概念1965年由著名的荷蘭電腦科學家Dijkstra提出,其基本思路是用一種新的變數型別(semaphore)來記錄當前可用資源的數量(可想象成停車場,共m個車位,來一輛車,則管理員用現有車位-1做測試,如果減1之後發現大於等於0,則把車放進去;如果減1之後發現小於0,則從門口排隊等著;走一輛車,則剩餘車位+1,如果小於等於0,則原先等待的車繼續等,如果大於0,則把原先等待的車放一輛進去)訊號量為0或正,則代表空閒資源數;為負則代表等待程序數。

    semaphore有兩種

實現方式:

    1) semaphore的取值必須大於或等於0。0表示當前已沒有空閒資源,而正數表示當前空閒資源的數量

    2) semaphore的取值可正可負,負數的絕對值表示正在等待進入臨界區的程序個數

    訊號量是由作業系統來維護的,使用者程序只能通過初始化和兩個標準原語(P、V原語)來訪問。初始化可指定一個非負整數,即空閒資源總數

    P原語:P是荷蘭語Proberen(測試)

的首字母。為阻塞原語,負責把當前程序由執行狀態轉換為阻塞狀態,直到另外一個程序喚醒它。操作為:申請一個空閒資源(把訊號量減1),若成功,則退出;若失敗,則該程序被阻塞

    V原語:V是荷蘭語Verhogen(增加)的首字母。為喚醒原語,負責把一個被阻塞的程序喚醒,它有一個引數表,存放著等待被喚醒的程序資訊。操作為:釋放一個被佔用的資源(把訊號量加1),如果發現有被阻塞的程序,則選擇一個喚醒之

    P原語操作的動作是:     

    (1)sem減1;     

    (2)若sem減1後仍大於或等於零,則程序繼續執行;     

    (3)若sem減1後小於零,則該程序被阻塞後進入與該訊號相對應的佇列中,然後轉程序排程。

    V原語操作的動作是:     

    (1)sem加1;     

    (2)若相加結果大於零,則程序繼續執行;     

    (3)若相加結果小於或等於零,則從該訊號的等待佇列中喚醒一等待程序,然後再返回原程序繼續執行或轉程序排程。

    PV操作對於每一個程序來說,都只能進行一次,而且必須成對使用。在PV原語執行期間不允許有中斷的發生

    具體PV原語對訊號量的操作可以分為三種情況:

    1) 把訊號量視為一個加鎖標誌位,實現對一個共享變數的互斥訪問。

    實現過程:

    P(mutex); // mutex的初始值為1 訪問該共享資料;

    V(mutex);

    非臨界區;

    2) 把訊號量視為是某種型別的共享資源的剩餘個數,實現對一類共享資源的訪問。

    實現過程:

    P(resource); // resource的初始值為該資源的個數N 使用該資源;

    V(resource);

    非臨界區;

 3) 把訊號量作為程序間的同步工具

    實現過程:

    臨界區C1;

    P(S);

    V(S);

    臨界區C2;

舉例說明:

    例1:某超市門口為顧客準備了100輛手推車,每位顧客在進去買東西時取一輛推車,在買完東西結完帳以後再把推車還回去。試用P、V操作正確實現顧客程序的同步互斥關係。

    分析:把手推車視為某種資源,每個顧客為一個要互斥訪問該資源的程序。因此這個例子為PV原語的第二種應用型別。

    解:

semaphore S_CartNum;    // 空閒的手推車數量,初值為100

void  consumer(void)    // 顧客程序

{     

   P(S_CartNum);

   買東西;

   結帳;

   V(S_CartNum); 

}

    例2:桌子上有一個水果盤,每一次可以往裡面放入一個水果。爸爸專向盤子中放蘋果,兒子專等吃盤子中的蘋果。把爸爸、兒子看作二個程序,試用P、V操作使這兩個程序能正確地併發執行。

    分析:爸爸和兒子兩個程序相互制約,爸爸程序執行完即往盤中放入蘋果後,兒子程序才能執行即吃蘋果。因此該問題為程序間的同步問題。

    解:

semaphore  S_PlateNum;  // 盤子容量,初值為1

semaphore  S_AppleNum;  // 蘋果數量,初值為0

void  father( )  // 父親程序 {

    while(1)

    {        

        P(S_PlateNum);//先減少盤子數,如果兒子不拿走蘋果,則盤子數為0,父親就要等待

        往盤子中放入一個蘋果;

        V(S_AppleNum);

    }  }

void  son( )   // 兒子程序 {

    while(1)

    {        

        P(S_AppleNum);//先減少蘋果數,如果父親不放蘋果,則蘋果數為0,兒子就要等待

        從盤中取出蘋果;

        V(S_PlateNum);

        吃蘋果;

    }  }

    另附用PV原語解決程序同步與互斥問題的例子:

    例3:兩個程序PA、PB通過兩個FIFO(先進先出)緩衝區佇列連線(如圖)

    pv.JPG     

PA從Q2取訊息,處理後往Q1發訊息;PB從Q1取訊息,處理後往Q2發訊息,每個緩衝區長度等於傳送訊息長度。 Q1佇列長度為n,Q2佇列長度為m. 假設開始時Q1中裝滿了訊息,試用P、V操作解決上述程序間通訊問題。

    解:

// Q1隊列當中的空閒緩衝區個數,初值為0 semaphore  S_BuffNum_Q1;  

// Q2隊列當中的空閒緩衝區個數,初值為m  semaphore  S_BuffNum_Q2;    

// Q1隊列當中的訊息數量,初值為n  semaphore  S_MessageNum_Q1;

// Q2隊列當中的訊息數量,初值為0  semaphore  S_MessageNum_Q2;

void PA( ) {

    while(1)

    {         

      P(S_MessageNum_Q2);

        從Q2當中取出一條訊息;

        V(S_BuffNum_Q2);

        處理訊息;

        生成新的訊息;

        P(S_BuffNum_Q1);

        把該訊息傳送到Q1當中;

        V(S_MessageNum_Q1);

    }  }

void PB( ) {

    while(1)

    {         

       P(S_MessageNum_Q1);

        從Q1當中取出一條訊息;

        V(S_BuffNum_Q1);

        處理訊息;

        生成新的訊息;

        P(S_BuffNum_Q2);

        把該訊息傳送到Q2當中;

        V(S_MessageNum_Q2);

    }  }

    例4:《作業系統》課程的期末考試即將舉行,假設把學生和監考老師都看作程序,學生有N人,教師1人。考場門口每次只能進出一個人,進考場的原則是先來先進。當N個學生都進入了考場後,教師才能髮捲子。學生交卷後即可離開考場,而教師要等收上來全部卷子並封裝卷子後才能離開考場。

    (1)問共需設定幾個程序?

    (2)請用P、V操作解決上述問題中的同步和互斥關係。

    解:

semaphore  S_Door;         // 能否進出門,初值1

semaphore  S_StudentReady; // 學生是否到齊,初值為0

semaphore  S_ExamBegin;   // 開始考試,初值為0

semaphore  S_ExamOver;    // 考試結束,初值為0

int nStudentNum = 0;       // 學生數目

semaphore  S_Mutex1;       //互斥訊號量,初值為1

int  nPaperNum = 0;       // 已交的卷子數目

semaphore  S_Mutex2;       //互斥訊號量,初值為1

void  student( ) {

    P(S_Door);

    進門;     V(S_Door);

    P(S_Mutex1);

    nStudentNum ++;   // 增加學生的個數

    if(nStudentNum == N)  V(S_StudentReady);

    V(S_Mutex1);

    P(S_ExamBegin);  // 等老師宣佈考試開始

    考試中…

    交卷;

    P(S_Mutex2);

    nPaperNum ++;    // 增加試卷的份數

        if(nPaperNum == N) 

        V(S_ExamOver);

        V(S_Mutex2);

        P(S_Door);

        出門;

        V(S_Door);

}

void  teacher( ) {

    P(S_Door);

    進門;     V(S_Door);

    P(S_StudentReady);    //等待最後一個學生來喚醒

    髮捲子;

        for(i = 1; i <= N; i++)   

        V(S_ExamBegin);

        P(S_ExamOver);    //等待考試結束

        封裝試卷;

        P(S_Door);

        出門;         V(S_Door);

}

     5:某商店有兩種食品A和B,最大數量均為m個。 該商店將A、B兩種食品搭配出售,每次各取一個。為避免食品變質,遵循先到食品先出售的原則。有兩個食品公司分別不斷地供應A、B兩種食品(每次一個)。為保證正常銷售,當某種食品的數量比另一種的數量超過k(k<m)個時,暫停對數量大的食品進貨,補充數量少的食品。

    (1) 問共需設定幾個程序?

    (2) 用P、V操作解決上述問題中的同步互斥關係。

    解:

semaphore  S_BuffNum_A;  //A的緩衝區個數, 初值m

semaphore  S_Num_A;      // A的個數,初值為0

semaphore  S_BuffNum_B;  //B的緩衝區個數, 初值m

semaphore  S_Num_B;      // B的個數,初值為0

void  Shop( ) {

    while(1)

    {         P(S_Num_A);

        P(S_Num_B);

        分別取出A、B食品各一個;

        V(S_BuffNum_A);

        V(S_BuffNum_A);

        搭配地銷售這一對食品;

    }  }

// “A食品加1,而B食品不變”這種情形允許出現的次數(許可證的數量),其值等於//k-(A-B),初值為k

semaphore  S_A_B;

// “B食品加1,而A食品不變”這種情形允許出現的次數(許可證的數量),其值等於//k-(B-A),初值為k

semaphore  S_B_A;

void  Producer_A( ) {

    while(1)

    {         生產一個A食品;

        P(S_BuffNum_A);

        P(S_A_B);

        向商店提供一個A食品;

        V(S_Num_A);

        V(S_B_A);

    }  }

void  Producer_B( ) {

    while(1)

    {         生產一個B食品;

        P(S_BuffNum_B);

        P(S_B_A);

        向商店提供一個B食品;

        V(S_Num_B);

        V(S_A_B);

    }  }

    例6:在一棟學生公寓裡,只有一間浴室,而且這間浴室非常小,每一次只能容納一個人。公寓裡既住著男生也住著女生,他們不得不分享這間浴室。因此,樓長制定了以下的浴室使用規則:

     (1)每一次只能有一個人在使用;

     (2)女生的優先順序要高於男生,即如果同時有男生和女生在等待使用浴室,則女生優先;

     (3)對於相同性別的人來說,採用先來先使用的原則。

    假設:

    (1)當一個男生想要使用浴室時,他會去執行一個函式boy_wants_to_use_bathroom,當他離開浴室時,也會去執行另外一個函式boy_leaves_bathroom;

    (2)當一個女生想要使用浴室時,會去執行函式girl_wants_to_use_bathroom,當她離開時, 也會執行函式girl_leaves_bathroom;

    問題:請用訊號量和P、V操作來實現這四個函式(初始狀態:浴室是空的)。

    解:

訊號量的定義:

semaphore  S_mutex;  // 互斥訊號量,初值均為1

semaphore  S_boys;   // 男生等待佇列,初值為0

semaphore  S_girls; // 女生等待佇列,初值為0

普通變數的定義:

int  boys_waiting = 0;  // 正在等待的男生數;

int  girls_waiting = 0; // 正在等待的女生數;

int  using = 0;         // 當前是否有人在使用浴室;

void  boy_wants_to_use_bathroom ( ) {

    P(S_mutex);

    if((using == 0) && (girls_waiting == 0))

        {

            using  =  1;

            V(S_mutex);

        }

    else

        {

            boys_waiting ++;

            V(S_mutex);

            P(S_boys);

        }

}

void  boy_leaves_bathroom ( ) {

    P(S_mutex);

    if(girls_waiting  >  0)  // 優先喚醒女生

        {

            girls_waiting --;

            V(S_girls);

        }

    else  if(boys_waiting  >  0)

        {

            boys_waiting --;

            V(S_ boys);

        }

        else   

            using  =  0;     // 無人在等待

            V(S_mutex);

}

void  girl_wants_to_use_bathroom ( ) {

    P(S_mutex);

    if(using == 0)

    {

        using  =  1;

        V(S_mutex);

    }

    else

    {

        girls_waiting ++;

        V(S_mutex);

        P(S_girls);

    }

}

void  girl_leaves_bathroom ( ) {

    P(S_mutex);

    if(girls_waiting  >  0)  // 優先喚醒女生

    {

        girls_waiting --;

        V(S_girls);

    }

    else  if(boys_waiting  >  0)

        {

            boys_waiting --;

            V(S_ boys);

        }

        else   

            using  =  0;     // 無人在等待

            V(S_mutex);

}