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名購票者進入,當售票廳中少於20名購票者時,廳外的購票者可立即進入,否則需要在外面等待。每個購票者可看成一個進程。
分析:第一步:確定進程間的關系。售票廳是各進程共享的公有資源,當售票廳中多於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的緩存區。這個例子在很多書中都有介紹,在這裡就不說了。