如何用PV原語實現程序間的互斥與同步(轉載)
PV原語的含義
P操作和V操作是不可中斷的程式段,稱為原語。PV原語及訊號量的概念都是由荷蘭科學家E.W.Dijkstra提出的。訊號量sem是一整數,sem大於等於零時代表可供併發程序使用的資源實體數,但sem小於零時則表示正在等待使用臨界區的程序數。
P原語操作的動作是:
(1)sem減1;
(2)若sem減1後仍大於或等於零,則程序繼續執行;
(3)若sem減1後小於零,則該程序被阻塞後進入與該訊號相對應的佇列中,然後轉程序排程。
V原語操作的動作是:
(1)sem加1;
(2)若相加結果大於零,則程序繼續執行;
(3)若相加結果小於或等於零,則從該訊號的等待佇列中喚醒一等待程序,然後再返回原程序繼續執行或轉程序排程。
PV操作對於每一個程序來說,都只能進行一次,而且必須成對使用。在PV原語執行期間不允許有中斷的發生。
用PV原語實現程序的互斥
由於用於互斥的訊號量sem與所有的併發程序有關,所以稱之為公有訊號量。公有訊號量的值反映了公有資源的數量。只要把臨界區置於P(sem)和V(sem)之間,即可實現程序間的互斥。就象火車中的每節車廂只有一個衛生間,該車廂的所有旅客共享這個公有資源:衛生間,所以旅客間必須互斥進入衛生間,只要把衛生間放在P(sem)和V(sem)之間,就可以到達互斥的效果。以下例子說明程序的互斥實現。
例1
生產圍棋的工人不小心把相等數量的黑子和白子混裝載一個箱子裡,現要用自動分揀系統把黑子和白子分開,該系統由兩個併發執行的程序組成,功能如下:
(1)程序A專門揀黑子,程序B專門揀白子;
(2)每個程序每次只揀一個子,當一個程序在揀子時不允許另一個程序去揀子;
分析:
第一步:確定程序間的關係。由功能(2)可知程序之間是互斥的關係。
第二步:確定訊號量及其值。由於程序A和程序B要互斥進入箱子去揀棋子,箱子是兩個程序的公有資源,所以設定一個訊號量s,其值取決於公有資源的數目,由於箱子只有一個,s的初值就設為1。
實現:
begin
s:semaphore;
s:=1;
cobegin
process A
begin
L1: P(s);
揀黑子;
V(s);
goto L1;
end;
process B
begin
L2:P(s);
揀白子;
V(s);
goto L2;
end;
coend;
end;
判斷程序間是否互斥,關鍵是看程序間是否共享某一公有資源,一個公有資源與一個訊號量相對應。確定訊號量的值是一個關鍵點,它代表了可用資源實體數。如下例項:
例2
分析:第一步:確定程序間的關係。售票廳是各程序共享的公有資源,當售票廳中多於20名購票者時,廳外的購票者需要在外面等待。所以程序間是互斥的關係。第二步:確定訊號量及其值。只有一個公有資源:售票廳,所以設定一個訊號量s。售票廳最多容納20個程序,即可用資源實體數為20,s的初值就設為20。
實現:
begin
s:semaphore;
s:=20;
cobegin
process PI(I=1,2,……)
begin P(s);
進入售票廳;
購票;
退出;
V(s);
end;
coend
當購票者進入售票廳前要執行P(s)操作,執行後若s大於或等於零,說明售票廳的人數還未滿可進入。執行後若s小於零,則說明售票廳的人數已滿不能進入。這個實現中同時最多允許20個程序進入售票廳購票,其餘程序只能等待。
用PV原語實現程序的同步
與程序互斥不同,程序同步時的訊號量只與制約程序及被制約程序有關而不是與整組併發程序有關,所以稱該訊號量為私有訊號量。利用PV原語實現程序同步的方法是:首先判斷程序間的關係為同步的,且為各併發程序設定私有訊號量,然後為私有訊號量賦初值,最後利用PV原語和私有訊號量規定各程序的執行順序。下面我們將例1增添一個條件,使其成為程序間是同步的。
例3
在例1的基礎之上再新增一個功能:
(3)當一個程序揀了一個棋子(黑子或白子)以後,必讓另一個程序揀一個棋子(黑子或白子)。
分析:
第一步:確定程序間的關係。由功能(1)(2)(3)可知,程序間的關係為同步關係。第二步:確定訊號量及其值。程序A和B共享箱子這個公有資源,但規定兩個程序必須輪流去取不同色的棋子,因而相互間要互通訊息。對於程序A可設定一個私有訊號量s1,該私有訊號量用於判斷程序A是否能去揀黑子,初值為1。對於程序B同樣設定一個私有訊號量s2,該私有訊號量用於判斷程序B是否能去揀白子,初值為0。當然你也可以設定s1初值為0,s2初值為1。
實現:
begin
s1,s2:semaphore;
s1:=1;s2:=0;
cobegin
process A
begin
L1: P(s1);
揀黑子;
V(s2);
goto L1;
end;
process B
begin
L2:P(s2);
揀白子;
V(s1);
goto L2;
end;
coend;
end;
另外一個問題就是P原語是不是一定在V原語的前面?回答是否定的。下面看一個例子。
例4
設在公共汽車上,司機和售票員的活動分別是:司機:啟動車輛,正常行車,到站停車。售票員:上乘客,關車門,售票,開車門,下乘客。用PV操作對其控制。
分析:
第一步:確定程序間的關係。司機到站停車後,售票員方可工作。同樣,售票員關車門後,司機才能工作。所以司機與售票員之間是一種同步關係。
第二步:確定訊號量及其值。由於司機與售票員之間要互通訊息,司機程序設定一個私有訊號量run,用於判斷司機能否進行工作,初值為0。售票員程序設定一個私有訊號量stop,用於判斷是否停車,售票員是否能夠開車門,初值為0。
實現:
begin stop ,run:semaphore
stop:=0;run:=0;
cobegin
driver: begin
L1: P(run);
啟動車輛;
正常行車;
到站停車;
V(stop);
goto L1;
end;
conductor:begin
L2:上乘客;
關車門;
V(run);
售票;
P(stop);
開車門;
下乘客;
goto L2;
end;
coend;
end;
用PV操作還可以實現程序同步與互斥的混合問題,典型的如:多個生產者和多個消費者共享容量為n的快取區。這個例子在很多書中都有介紹,在這裡就不說了。
PV原語
PV原語通過操作訊號量來處理程序間的同步與互斥的問題。其核心就是一段不可分割不可中斷的程式。
訊號量的概念1965年由著名的荷蘭電腦科學家Dijkstra提出,其基本思路是用一種新的變數型別(semaphore)來記錄當前可用資源的數量。有兩種實現方式:1)semaphore的取值必須大於或等於0。0表示當前已沒有空閒資源,而正數表示當前空閒資源的數量;2) semaphore的取值可正可負,負數的絕對值表示正在等待進入臨界區的程序個數。
訊號量是由作業系統來維護的,使用者程序只能通過初始化和兩個標準原語(P、V原語)來訪問。初始化可指定一個非負整數,即空閒資源總數。
P原語:P是荷蘭語Proberen(測試)的首字母。為阻塞原語,負責把當前程序由執行狀態轉換為阻塞狀態,直到另外一個程序喚醒它。操作為:申請一個空閒資源(把訊號量減1),若成功,則退出;若失敗,則該程序被阻塞;
V原語:V是荷蘭語Verhogen(增加)的首字母。為喚醒原語,負責把一個被阻塞的程序喚醒,它有一個引數表,存放著等待被喚醒的程序資訊。操作為:釋放一個被佔用的資源(把訊號量加1),如果發現有被阻塞的程序,則選擇一個喚醒之。
具體PV原語對訊號量的操作可以分為三種情況:
1)把訊號量視為一個加鎖標誌位,實現對一個共享變數的互斥訪問。
實現過程:
P(mutex); // mutex的初始值為1 訪問該共享資料;
V(mutex);
非臨界區
2)把訊號量視為是某種型別的共享資源的剩餘個數,實現對一類共享資源的訪問。
實現過程:
P(resource); // resource的初始值為該資源的個數N 使用該資源;
V(resource); 非臨界區
3)把訊號量作為程序間的同步工具
實現過程:
臨界區C1;
P(S);
V(S);
臨界區C2;
PV原語操作
訊號量
訊號量是最早出現的用來解決程序同步與互斥問題的機制,包括一個稱為訊號量的變數及對它進行的兩個原語操作。
本節將從以下幾個方面進行介紹--
--------------------------------------------------------------------------------
一. 訊號量的概念
1. 訊號量的型別定義
2. PV原語
--------------------------------------------------------------------------------
二. 例項
1. 生產者-消費者問題(有buffer)
2. 第一類讀-寫者問題
3. 哲學家問題
--------------------------------------------------------------------------------
一. 訊號量的概念
1. 訊號量的型別定義
每個訊號量至少須記錄兩個資訊:訊號量的值和等待該訊號量的程序佇列。它的型別定義如下:(用類PASCAL語言表述) semaphore = record value: integer; queue: ^PCB; end; 其中PCB是程序控制塊,是作業系統為每個程序建立的資料結構。 s.value>=0時,s.queue為空; s.value<0時,s.value的絕對值為s.queue中等待程序的個數;
返回
--------------------------------------------------------------------------------
2. PV原語
對一個訊號量變數可以進行兩種原語操作:p操作和v操作,定義如下: procedure p(var s:samephore); { s.value=s.value-1; if (s.value<0) asleep(s.queue); } procedure v(var s:samephore); { s.value=s.value+1; if (s.value<=0) wakeup(s.queue); } 其中用到兩個標準過程: asleep(s.queue);執行此操作的程序的PCB進入s.queue尾部,程序變成等待狀態
wakeup(s.queue);將s.queue頭程序喚醒插入就緒佇列 s.value初值為1時,可以用來實現程序的互斥。 p操作和v操作是不可中斷的程式段,稱為原語。如果將訊號量看作共享變數,則pv操作為其臨界區,多個程序不能同時執行,一般用硬體方法保證。一個訊號量只能置一次初值,以後只能對之進行p操作或v操作。由此也可以看到,訊號量機制必須有公共記憶體,不能用於分散式作業系統,這是它最大的弱點。
返回
--------------------------------------------------------------------------------
二. 例項
1. 生產者-消費者問題(有buffer)
問題描述: 一個倉庫可以存放K件物品。生產者每生產一件產品,將產品放入倉庫,倉庫滿了就停止生產。消費者每次從倉庫中去一件物品,然後進行消費,倉庫空時就停止消費。解答: 程序:Producer - 生產者程序,Consumer - 消費者程序 共有的資料結構: buffer: array [0..k-1] of integer; in,out: 0..k-1; — in記錄第一個空緩衝區,out記錄第一個不空的緩衝區 s1,s2,mutex: semaphore; — s1控制緩衝區不滿,s2控制緩衝區不空,mutex保護臨界區;
初始化s1=k,s2=0,mutex=1 producer(生產者程序): Item_Type item; { while (true) { produce(&item); p(s1); p(mutex); buffer[in]:=item; in:=(in+1) mod k; v(mutex); v(s2); } } consumer(消費者程序): Item_Type item; { while (true) { p(s2); p(mutex); item:=buffer[out]; out:=(out+1)
mod k; v(mutex); v(s1); consume(&item); } } 例程演示
返回
--------------------------------------------------------------------------------
2. 第一類讀-寫者問題
問題描述: 一些讀者和一些寫者對同一個黑板進行讀寫。多個讀者可同時讀黑板,但一個時刻只能有一個寫者,讀者寫者不能同時使用黑板。對使用黑板優先順序的不同規定使讀者-寫者問題又可分為幾類。第一類問題規定讀者優先順序較高,僅當無讀者時允許寫者使用黑板。解答: 程序:writer - 寫者程序,reader - 讀者程序 共有的資料結構: read_account:integer; r_w,mutex: semaphore; — r_w控制誰使用黑板,mutex保護臨界區,初值都為1 reader - (讀者程序): {
while (true) { p(mutex); read_account++; if(read_account=1) p(r_w); v(mutex); read(); p(mutex); read_account--; if(read_account=0) v(r_w);; v(mutex); } } writer - (寫者程序): { while (true) { p(mutex); write(); v(mutex); } } 例程演示
返回
--------------------------------------------------------------------------------
3. 哲學家問題
問題描述: 一個房間內有5個哲學家,他們的生活就是思考和進食。房間裡有一張圓桌,中間放著一盤通心粉(假定通心粉無限多)。桌子周圍放有五把椅子,分別屬於五位哲學家每兩位哲學家之間有一把叉子,哲學家進食時必須同時使用左右兩把叉子。解答: 程序:philosopher - 哲學家 共有的資料結構&過程: state: array [0..4] of (think,hungry,eat); ph: array [0..4] of semaphore; — 每個哲學家有一個訊號量,初值為0 mutex: semaphore;
— mutex保護臨界區,初值=1 procedure test(i:0..4); { if ((state[i]=hungry) and (state[(i+1)mod 5]<>eating) and (state[(i-1)mod 5]<>eating)) { state[i]=eating; V(ph[i]); } } philosopher(i:0..4): { while (true) { think(); p(mutex); state[i]=hungry; test(i); v(mutex);
p(ph[i]); eat(); p(mutex); state[i]=think; test((i-1) mod 5); test((i+1) mod 5); v(mutex); } }
若有一個倉庫,可以存放P1、P2兩種產品,但是每次只能存放一種產品.要求:
① w=P1的數量-P2的數量
② -i<w<k (i、k為正整數)
若用PV操作實現P1和P2產品的入庫過程,至少需要__(23)__個同步訊號量及__(24)__個互斥訊號量,其中,同步訊號量的初值分別為__(25)__,互斥訊號量的初值分別為__(26)__。
(23)A.0 B.1 C.2 D.3
(24)A.0 B.1 C.2 D.3
(25)A.0 B.i,k,0 C.i,k D.i-1,k-1
(26)A.1 B.1,1 C.1,1,1 D.i,k
答案是C B D A
系分考試知識:PV操作釋疑
訊號量
訊號量是最早出現的用來解決程序同步與互斥問題的機制,
包括一個稱為訊號量的變數及對它進行的兩個原語操作。
一. 訊號量的概念
1.訊號量的型別定義
每個訊號量至少須記錄兩個資訊:訊號量的值和等待該訊號量的程序佇列。它的型別定義如下:(用類PASCAL語言表述)
semaphore = record
value: integer;
queue: ^PCB;
end;
其中PCB是程序控制塊,是作業系統為每個程序建立的資料結構。
s.value>=0時,s.queue為空;
s.value<0時,s.value的絕對值為s.queue中等待程序的個數;
2.PV原語
對一個訊號量變數可以進行兩種原語操作:p操作和v操作,定義如下: procedure p(var s:samephore);
{
s.value=s.value-1;
if (s.value<0) asleep(s.queue);
}
procedure v(var s:samephore);
{
s.value=s.value+1;
if (s.value<=0) wakeup(s.queue);
}
其中用到兩個標準過程:
asleep(s.queue);執行此操作的程序的PCB進入s.queue尾部,程序變成等待狀態
wakeup(s.queue);將s.queue頭程序喚醒插入就緒佇列
s.value初值為1時,可以用來實現程序的互斥。
p操作和v操作是不可中斷的程式段,稱為原語。如果將訊號量看作共享變數,則pv操作為其臨界區,多個程序不能同時執行,一般用硬體方法保證。一個訊號量只能置一次初值,以後只能對之進行p操作或v操作。
由此也可以看到,訊號量機制必須有公共記憶體,不能用於分散式作業系統,這是它最大的弱點。
V原語的主要操作是:
(1)sem加1;
(2)若相加結果大於零,則程序繼續執行;
(3)若相加結果小於或等於零,則喚醒一阻塞在該訊號量上的程序,然後再返回原程序繼續執行或轉程序排程。
典型理解偏差:
一,以V原語的1、2步來做,Sem不就永遠大於0,那程序不就一直迴圈執行成為死迴圈了?
二,Sem大於0那就表示有臨界資源可供使用,為什麼不喚醒程序?
三,Sem小於0應該是說沒有臨界資源可供使用,為什麼還要喚醒程序?
析疑: 一,P操作對sem減1的。P、V原語必須成對使用!從而不會造成死迴圈。 二,Sem大於0的確表示有臨界資源可供使用,而且這個時候沒有程序被阻塞在這個資源上,也就是說沒有程序因為得不到這類資源而阻塞,所以沒有被阻塞的程序,自然不需要喚醒。 三,V原語操作的本質在於:一個程序使用完臨界資源後,釋放臨界資源,使Sem加1,以通知其它的程序,這個時候如果Sem<0,表明有程序阻塞在該類資源上,因此要從阻塞佇列裡喚醒一個程序來“轉手”該類資源。 比如,有2個某類資源,三個程序A、B、C、D要用該類資源,最開始Sem=2,當A進入,Sem=1,當B進入Sem=0,表明該類資源剛好用完, 當C進入時Sem=-1,表明有一個程序被阻塞了,D進入,Sem=-2。當A用完該類資源時,進行V操作,Sem=-1,釋放該類資源,而這時Sem<0,表明有程序阻塞在該類資源上,於是喚醒一個。
為了進一步加深理解,再引入二個問題: 四,如果是互斥訊號量的話,應該設定訊號量Sen=1,但是當有5個程序都訪問的話,最後在該訊號量的連結串列裡會有4個在等待,也是說S=-4,那麼第一個程序執行了V操作使S加1,釋放了資源,下一個應該能夠執行,但喚醒的這個程序在執行P操作時因S〈0 ,也還是執行不了,這是怎麼回事呢? 五,Sem的絕對值表示等待的程序數,同時又表示臨界資源,這到底是怎麼回事? 析疑: 四,當一個程序阻塞了的時候,它已經執行過了P操作,並卡在臨界區那個地方。當喚醒它時就立即進入它自己的臨界區,並不需要執行P操作了,當執行完了臨界區的程式後,就執行V操作。 五,當訊號量Sem小於0時,其絕對值表示系統中因請求該類資源而被阻塞的程序數目.S大於0時表示可用的臨界資源數。注意在不同情況下所表達的含義不一樣。當等於0時,表示剛好用完。
吸菸者問題(Patil, 1971)
三個吸菸者在一間房間內,還有一個香菸供應者。為了製造並抽掉香菸,每個吸菸者需要三樣東西:菸草、紙和火柴。供應者有豐富的貨物提供。三個吸菸者中,第一個有自己的菸草,第二個有自己的紙,第三個有自己的火柴。供應者將兩樣東西放在桌子上,允許一個吸菸者進行對健康不利的吸菸。當吸菸者完成吸菸後喚醒供應者,供應者再放兩樣東西(隨機地)在桌面上,然後喚醒另一個吸菸者。試為吸菸者和供應者編寫程式解決問題。
分析思想:
1) 供應者seller隨即產生兩樣東西,提供它們,這裡用普通變數來表示
2) 吸菸者程序smoker根據其排號不同,擁有不同的一件東西。假設1號吸菸者擁有菸草tobacco,2號吸菸者擁有紙paper,3號吸菸者擁有火柴match。其他號碼錯誤返回。
3) 吸菸者的序號代表他們擁有的東西,用他們的序號和供應者產生的兩樣東西比較,如果都不相等,則說明他擁有的東西和供應者產生的東西匹配,它可以吸菸。如果其中一個相等,則推出,繼續排隊。
4) mutex資訊量代表一個只能進入的門,每次只有一個吸菸者可以進入進行比較和吸菸。
5) 每個吸菸者在吸菸完畢之後出門之前要叫醒供應者,呼叫seller程序。
資訊量:
mutex:=1 ——互斥資訊量,表示吸菸者進入的門
解法:
#typedef int semaphore
semaphore mutex=1;
int thing1,thing2;
void seller(){
Randomize;
int i = random(2); //產生0-2的隨機數
thing1 = ((i-1)%3)+1; //將thing賦值為0-2種不等於i的兩個數+1
thing2 = ((i+1)%3)+1;
}
void smoker(int i){
if((i<=0)||(i>3)) //i值應為1-3
return false;
while(true){
P(mutex)
if((i!=thing1)&&(i!=thing2)){ //所擁有的物品與供應者放出的物品不一樣
吸菸;
seller(); //喚醒供應者
V(mutex);
}
else{
V(mutex)
}//end if
}//end while
}
用pv原語完成下列題目:
(1)在南開大學和天津大學之間有一條彎曲的小路,其中從S到T一段路每次只允許一輛自行車通過,但中間有一個小的“安全島”M(同時允許兩輛自行車停留),可供兩輛自行車已從兩端進入小路情況下錯車使用,如圖所示。試設計一個演算法來使來往的自行車均可順利通過。
(2)5.某高校計算機系開設網路課並安排上機實習,假設機房共有2m臺機器,有2n名學生選課(m,n均大於等於1),規定:
1,每兩個學生組成一組,各佔一臺及其協同完成上機實習;
2,只有一組兩個學生到齊,並且此時機房有空閒機器時,該組學生才能進入機房;
3,上機實習由一名教師檢查,檢查完畢,一組學生同時離開機房
試用P、V操縱模擬上機實習過程。
(3)某寺廟,有小和尚和老和尚若干,有一個水缸,由小和尚提水入缸供老和尚飲用。水缸可以容納10桶水,水取自同一口井中,由於水井口窄,每次只能容納一個水桶取水。水桶總數為3個(老和尚和小和尚共同使用)。每次入水、取水僅為一桶,且不可同時進行。試給出有關取水、入水的演算法描述。
(4)設公共汽車上,司機和售票員的活動分別是:
司機: 售票員:
啟動車輛 上下乘客
正常行車 關車門
到站停車 售票
開車門
上下乘客
在汽車不斷到站,停車,行駛過程中,這兩個活動的同步關係
1) 設訊號量 sk=1(表示有無車通過1表示有車)
ts=1(.........)
st=1(.......)
lt=1(......)
m=2(.......)
執行p操作
從s到t 從t到s
p(st) p(ts)
p(sk) p(lt)
從s到k 從l到t
p(m) p(m)
進入m 進入m
v(sk) v(lt)
p(lt) p(sk)
從l到t 從k到s
v(st) v(ts)
v(lt) v(sk)
begin stop ,run:semaphore
stop:=0;run:=0;
cobegin
driver: begin
L1: P(run);
啟動車輛;
正常行車;
到站停車;
V(stop);
goto L1;
end;
conductor:begin
L2:上乘客;
關車門;
V(run);
售票;
P(stop);
開車門;
下乘客;
goto L2;
end;
coend;
end;