PV原語小結及程序同步示例
訊號量S的物理含義
S>0:表示有S個資源可用;S=0表示無資源可用;S<0絕對值表示等待佇列或連結串列中的程序個數。訊號量的初值應大於等於0。
PV原語小結
通過操作訊號量來處理程序間的同步與互斥的問題。其核心就是一段不可分割不可中斷的程式。
訊號量是由作業系統來維護的,使用者程序只能通過初始化和兩個標準原語(P、V原語)來訪問,它們在執行時是不可中斷的。初始化可指定一個非負整數,即空閒資源總數。
P原語:P是荷蘭語Proberen(測試)的首字母。為阻塞原語,負責把當前程序由執行狀態轉換為阻塞狀態,直到另外一個程序喚醒它。操作為:申請一個空閒資源(把訊號量減1),則若成功,則退出;若失敗,則該程序被阻塞;
V原語:V是荷蘭語Verhogen(增加)的首字母。為喚醒原語,負責把一個被阻塞的程序喚醒,它有一個引數表,存放著等待被喚醒的程序資訊。操作為:釋放一個被佔用的資源(把訊號量加1),如果發現有被阻塞的程序,則選擇一個喚醒之。
P(S):表示申請一個資源,S減 1;若 減1 後仍S>=0,則該程序繼續執行;若 減1 後 S<0,表示已無資源可用,需要將自己阻塞起來。
V(S):表示釋放一個資源,S加 1;若 加1 後S>0,則該程序繼續執行;若 加1 後 S<=0,表示等待佇列上有等待程序,需要將第一個等待的程序喚醒。
PV原語對訊號量的操作可以分為三種情況:
.
1. 把訊號量視為一個加鎖標誌位,實現對一個共享變數的互斥訪問。
實現過程:
P(mutex); // mutex的初始值為1
訪問該共享資料
V(mutex);
非臨界區
互斥訊號量是根據臨界資源的型別設定的。有幾種型別的臨界資源就設定幾個互斥訊號量。它代表該類臨界資源的數量,或表示是否可用,其初值一般為“1”。
.
2. 把訊號量視為是某種型別的共享資源的剩餘個數,實現對一類共享資源的訪問。
實現過程:
P(resource); // resource的初始值為該資源的個數N
使用該資源;
V(resource);
非臨界區
.
3. 把訊號量作為程序間的同步工具
實現過程:
臨界區C1; P(S);
V(S); 臨界區C2;
同步訊號量是根據程序的數量設定的。一般情況下,有幾個程序就設定幾個同步訊號量,表示該程序是否可以執行,或表示該程序是否執行結束,其初值一般為“0”。
同步與互斥的解題思路
分清哪些是互斥問題(互斥訪問臨界資源的),哪些是同步問題(具有前後執行順序要求的)。
對互斥問題要設定互斥訊號量,不管有互斥關係的程序有幾個或幾類,通常只設置一個互斥訊號量,且初值為1,代表一次只允許一個程序對臨界資源訪問。
對同步問題要設定同步訊號量,通常同步訊號量的個數與參與同步的程序種類有關,即同步關係涉及幾類程序,就有幾個同步訊號量。同步訊號量表示該程序是否可以開始或該程序是否已經結束。
在每個程序中用於實現互斥的PV操作必須成對出現;用於實現同步的PV操作也必須成對出現,但可以分別出現在不同的程序中;在某個程序中如果同時存在互斥與同步的P操作,則其順序不能顛倒,必須先執行對同步訊號量的P操作,再執行對互斥訊號量的P操作,但V操作的順序沒有嚴格要求。
為什麼P操作不能顛倒?
解程序同步和互斥問題的方法步驟
1) 關係分析。找出問題中的程序數,並且分析它們之間的同步和互斥關係。同步、互斥關係直接按照上面的經典範式改寫。
2) 整理思路。找出解決問題的關鍵點,並且根據做過的題目找出解決的思路。根據程序的操作流程確定P操作、V操作的大致順序。
3) 設定訊號量。根據上面兩步,設定需要的訊號量,確定初值,完善整理。
生產者消費者問題
問題描述
一組生產者程序和一組消費者程序共享一個初始為空、大小為n的緩衝區,只有緩衝區沒滿時,生產者才能把訊息放入到緩衝區,否則必須等待;只有緩衝區不空時,消費者才能從中取出訊息,否則必須等待。由於緩衝區是臨界資源,它只允許一個生產者放入訊息,或者一個消費者從中取出訊息。
問題分析
關係分析:生產者往緩衝區寫訊息,,消費者從緩衝區取訊息必須分開進行,所以兩者是互斥關係;同時只有生產者放入訊息後,消費者才能從中取出訊息,所以兩者還是同步關係。
整理思路:兩個程序,既是互斥關係,又是同步關係。此時需要注意互斥同步PV操作的位置。
訊號量設定:互斥訊號量mutex,初值為1,保證互斥的訪問緩衝池;訊號量full記錄當前緩衝池中滿緩衝區個數,初值為0,訊號量empty記錄當前緩衝池空緩衝區個數,初值為n。
類C語言程式碼
semaphore mutex=1; //臨界區互斥訊號量
semaphore empty=n; //空閒緩衝區
semaphore full=0; //緩衝區初始化為空
producer () { //生產者程序
while(1){
produce an item in nextp; //生產資料
P(empty); //獲取空緩衝區單元
P(mutex); //進入臨界區.
add nextp to buffer; //將資料放入緩衝區
V(mutex); //離開臨界區,釋放互斥訊號量
V(full); //滿緩衝區數加1
}
}
consumer () { //消費者程序
while(1){
P(full); //獲取滿緩衝區單元
P(mutex); // 進入臨界區
remove an item from buffer; //從緩衝區中取出資料
V (mutex); //離開臨界區,釋放互斥訊號量
V (empty) ; //空緩衝區數加1
consume the item; //消費資料
}
}
生產者消費者問題擴充套件
問題描述
桌子上有一隻盤子,每次只能向其中放入一個水果。爸爸專向盤子中放蘋果,媽媽專向盤子中放橘子,兒子專等吃盤子中的橘子,女兒專等吃盤子中的蘋果。只有盤子為空時,爸爸或媽媽就可向盤子中放一個水果;僅當盤子中有自己需要的水果時,兒子或女兒可以從盤子中取出。
問題分析
關係分析:爸媽往盤子裡放水果,必須互斥進行,所以爸媽是互斥關係;爸爸和女兒、媽媽和兒子是同步關係,類似生產者和消費者;兒子和女兒沒有什麼關係,有相應需求的水果就拿走
整理思路:有四個程序:爸爸放蘋果、女兒吃蘋果、媽媽放橘子、兒子吃橘子,可抽象為兩對生產者消費者對一個緩衝區(盤子)進行操作
訊號量設定:盤子plate為互斥訊號量,表示是否允許向盤子中放置水果,初值為1, 表示允許放入。同步訊號量設定兩個,分別為apple、orange,因為兩類程序:爸爸和女兒取放蘋果、媽媽和兒子取放橘子。apple表示盤中是否有蘋果,初值為0,表示盤子為空,不可取,若apple為1可以取;orange類似。
類C語言程式碼
semaphore plate=1, apple=0, orange=0;
father() //父親程序
{
while(1)
{
P(plate); //互斥向盤中放水果
向盤中放蘋果;
V(apple); //放好後,此時可以取蘋果
}
}
mother() //母親程序
{
while(1)
{
P(plate); //互斥向盤中放水果
向盤中放橘子;
V(orange); //放好後,此時可以取橘子
}
}
sun() //兒子程序
{
while(1)
{
P(orange); //互斥從盤中取橘子
從盤中取橘子;
V(plate); //取完後盤子可用
}
}
daughter() //女兒程序
{
while(1)
{
P(apple); //互斥從盤中取蘋果
從盤中取蘋果;
V(plate); //取完後盤子可用
}
}
PV原語第二種型別示例
問題描述
某超市門口為顧客準備了100輛手推車,每位顧客在進去買東西時取一輛推車,在買完東西結完帳以後再把推車還回去。試用P、V操作正確實現顧客程序的同步互斥關係。
問題分析
把手推車視為某種資源,每個顧客為一個要互斥訪問該資源的程序。因此這個例子為PV原語的第二種應用型別。
類C程式碼
semaphore num=100;
consumer()
{
P(num);
買東西;
結帳;
V(num);
}
哲學家就餐問題
問題描述
一張圓桌上坐著5名哲學家,每兩個哲學家之間的桌上擺一根筷子,桌子的中間是一碗米飯,如圖所示。哲學家們傾注畢生精力用於思考和進餐,哲學家在思考時,並不影響他人。只有當哲學家飢餓的時候,才試圖拿起左、 右兩根筷子(一根一根地拿起)。如果筷子已在他人手上,則需等待。飢餓的哲學家只有同時拿到了兩根筷子才可以開始進餐,當進餐完畢後,放下筷子繼續思考。
問題分析
關係分析:5名哲學家與左右鄰居對其中間筷子的訪問是互斥關係。
整理思路:這裡有五個程序。本題的關鍵是如何讓一個哲學家拿到左右兩個筷子而不造成死鎖或者飢餓現象。那麼解決方法有兩個,一個是讓他們同時拿兩個筷子;二是對每個哲學家的動作制定規則,避免飢餓或者死鎖現象的發生。為了防止死鎖的發生,可以對哲學家程序施加一些限制條件,比如至多允許四個哲學家同時進餐;僅當一個哲學家左右兩邊的筷子都可用時才允許他抓起筷子;對哲學家順序編號,要求奇數號哲學家先抓左邊的筷子,然後再轉他右邊的筷子,而偶數號哲學家剛好相反。正解制定規則如下:假設釆用第二種方法,當一個哲學家左右兩邊的筷子都可用時,才允許他抓起筷子。
訊號量設定:定義互斥訊號量陣列Chopstick[5] = {l, 1, 1, 1, 1}用於對5個筷子的互斥訪問。取筷子的訊號量mutex,初值為1
類C語言程式碼
semaphore chopstick[5] = {1,1,1,1,1}; //初始化訊號量
semaphore mutex=1; //設定取筷子的訊號量
Pi(){ //i號哲學家的程序
while(1)
{
P (mutex) ; //在取筷子前獲得互斥量
P (chopstick [i]) ; //取左邊筷子
P (chopstick[ (i+1) %5]) ; //取右邊筷子
V (mutex) ; //釋放取筷子的訊號量
eat; //進餐
V(chopstick[i] ) ; //放回左邊筷子
V(chopstick[ (i+l)%5]) ; //放回右邊筷子
think; // 思考
}
}
會發生死鎖的演算法
semaphore chopstick[5] = {1,1,1,1,1}; //初始化訊號量
Pi(){ //i號哲學家的程序
while(1)
{
P (chopstick [i]) ; //取左邊筷子
P (chopstick[ (i+1) %5]) ; //取右邊筷子
eat; //進餐
V(chopstick[i] ) ; //放回左邊筷子
V(chopstick[ (i+l)%5]) ; //放回右邊筷子
think; // 思考
}
}
/*當每個哲學家同時拿起同一側的筷子時,會發生死鎖*/