原文地址:http://blog.csdn.net/speedme/article/details/17597373
上篇博客中(進程同步之臨界區域問題及Peterson算法),我們對臨界區,臨界資源,鎖機制詳細解讀了下,留下了一個問題,就是鎖機制只能判斷臨界資源是否被占用,所以他解決了互斥問題,但是他不能確定前面的進程是否完成,所以他不能用於同步問題中。下面就為你講解信號量機制是如何解決這一問題的。
1.信號量機制
原文地址:http://blog.csdn.net/speedme/article/details/17597373
什么是信號量?信號量(semaphore)的數據結構為一個值和一個指針,指針指向等待該信號量的下一個進程。信號量的值與相應資源的使用情況有關。
當它的值大於0時,表示當前可用資源的數量;
當它的值小於0時,其絕對值表示等待使用該資源的進程個數。
注意,信號量的值僅能由PV操作來改變。
一般來說,信號量S³0時,S表示可用資源的數量。執行一次P操作意味着請求分配一個單位資源,因此S的值減1;當S<0時,表示已經沒有可用資源,請求者必須等待別的進程釋放該類資源,它才能運行下去。而執行一個V操作意味着釋放一個單位資源,因此S的值加1;若S£0,表示有某些進程正在等待該資源,因此要喚醒一個等待狀態的進程,使之運行下去。2.PV操作
p操作(wait):申請一個單位資源,進程進入
經典偽代碼
wait(S){ while(s<=0)<span style="white-space:pre"> </span>//如果沒有資源則會循環等待; ; S-- ; }
v操作(signal):釋放一個單位資源,進程出來
signal(S){ S++ ; }
P(S):①將信號量S的值減1,即S=S-1;
②如果S<=0,則該進程繼續執行;否則該進程置為等待狀態,排入等待隊列。
V(S):①將信號量S的值加1,即S=S+1;
②如果S>0,則該進程繼續執行;否則釋放隊列中第一個等待信號量的進程。
(1)每個程序中用戶實現互斥的P、V操作必須成對出現,先做P操作,進臨界區,后做V操作,出臨界區。若有多個分支,要認真檢查其成對性。
(2)P、V操作應分別緊靠臨界區的頭尾部,臨界區的代碼應盡可能短,不能有死循環。
(3)互斥信號量的初值一般為1。
3.三個經典同步問題
a.生產者-消費者(緩沖區問題)

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) ;
b.作者讀者問題

我們可認為寫者之間、寫者與第一個讀者之間要對共享資源進行互斥訪問,而后續讀者不需要互斥訪問。為此,可設置兩個信號量Wmutex、Rmutex和一個公共變量Rcount。其中:Wmutex表示“允許寫”,初值是1;公共變量Rcount表示“正在讀”的進程數,初值是0;Rmutex表示對Rcount的互斥操作,初值是1。
在這個例子中,我們可見到臨界資源訪問過程的嵌套使用。在讀者算法中,進入區和退出區又分別嵌套了一個臨界資源訪問過程。
對讀者一寫者問題,也可采用一般“信號量集”機制來實現。如果我們在前面的讀寫操作限制上再加一個限制條件:同時讀的“讀者”最多R個。這時,可設置兩個信號量Wmutex和Rcount。其中:Wmutex表示“允許寫”,初值是¨Rcount表示“允許讀者數目”,初值為R。為采用一般“信號量集”機制來實現的讀者一寫者算法。
c.哲學家進餐問題
(1) 在什么情況下5 個哲學家全部吃不上飯?
考慮兩種實現的方式,如下:
A.
算法描述:
void 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); /*把右側筷子放回桌子*/ } }
分析:假如所有的哲學家都同時拿起左側筷子,看到右側筷子不可用,又都放下左側筷子,
等一會兒,又同時拿起左側筷子,如此這般,永遠重復。對於這種情況,即所有的程序都在
無限期地運行,但是都無法取得任何進展,即出現飢餓,所有哲學家都吃不上飯。
B.
算法描述:
規定在拿到左側的筷子后,先檢查右面的筷子是否可用。如果不可用,則先放下左側筷子,
等一段時間再重復整個過程。
分析:當出現以下情形,在某一個瞬間,所有的哲學家都同時啟動這個算法,拿起左側的筷
子,而看到右側筷子不可用,又都放下左側筷子,等一會兒,又同時拿起左側筷子……如此
這樣永遠重復下去。對於這種情況,所有的程序都在運行,但卻無法取得進展,即出現飢餓,
所有的哲學家都吃不上飯。
(2) 描述一種沒有人餓死(永遠拿不到筷子)算法。
考慮了四種實現的方式(A、B、C、D):
A.原理:至多只允許四個哲學家同時進餐,以保證至少有一個哲學家能夠進餐,最終總會釋
放出他所使用過的兩支筷子,從而可使更多的哲學家進餐。以下將room 作為信號量,只允
許4 個哲學家同時進入餐廳就餐,這樣就能保證至少有一個哲學家可以就餐,而申請進入
餐廳的哲學家進入room 的等待隊列,根據FIFO 的原則,總會進入到餐廳就餐,因此不會
出現餓死和死鎖的現象。
偽碼:
semaphore 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); } }
【例1】生產者-消費者問題
在多道程序環境下,進程同步是一個十分重要又令人感興趣的問題,而生產者-消費者問題是其中一個有代表性的進程同步問題。下面我們給出了各種情況下的生產者-消費者問題,深入地分析和透徹地理解這個例子,對於全面解決操作系統內的同步、互斥問題將有很大幫助。
(1)一個生產者,一個消費者,公用一個緩沖區。
定義兩個同步信號量:
empty——表示緩沖區是否為空,初值為1。
full——表示緩沖區中是否為滿,初值為0。
生產者進程
while(TRUE){
生產一個產品;
P(empty);
產品送往Buffer;
V(full);
}
消費者進程
while(True){
P(full);
從Buffer取出一個產品;
V(empty);
消費該產品;
}
(2)一個生產者,一個消費者,公用n個環形緩沖區。
定義兩個同步信號量:
empty——表示緩沖區是否為空,初值為n。
full——表示緩沖區中是否為滿,初值為0。
設緩沖區的編號為1~n-1,定義兩個指針in和out,分別是生產者進程和消費者進程使用的指
,指向下一個可用的緩沖區。
生產者進程
while(TRUE){
生產一個產品;
P(empty);
產品送往buffer(in);
in=(in+1)mod n;
V(full);
}
消費者進程
while(TRUE){
P(full);
從buffer(out)中取出產品;
out=(out+1)mod n;
V(empty);
消費該產品;
}
(3)一組生產者,一組消費者,公用n個環形緩沖區
在這個問題中,不僅生產者與消費者之間要同步,而且各個生產者之間、各個消費者之間還必須互斥地訪問緩沖區。
定義四個信號量:
empty——表示緩沖區是否為空,初值為n。
full——表示緩沖區中是否為滿,初值為0。
mutex1——生產者之間的互斥信號量,初值為1。
mutex2——消費者之間的互斥信號量,初值為1。
設緩沖區的編號為1~n-1,定義兩個指針in和out,分別是生產者進程和消費者進程使用的指針,指向下一個可用的緩沖區。
生產者進程
while(TRUE){
生產一個產品;
P(empty);
P(mutex1);
產品送往buffer(in);
in=(in+1)mod n;
V(mutex1);
V(full);
}
消費者進程
while(TRUE){
P(full)
P(mutex2);
從buffer(out)中取出產品;
out=(out+1)mod n;
V(mutex2);
V(empty);
消費該產品;
}
需要注意的是無論在生產者進程中還是在消費者進程中,兩個P操作的次序不能顛倒。應先執行同步信號量的P操作,然后再執行互斥信號量的P操作,否則可能造成進程死鎖。
【例2】桌上有一空盤,允許存放一只水果。爸爸可向盤中放蘋果,也可向盤中放桔子,兒子專等吃盤中的桔子,女兒專等吃盤中的蘋果。規定當盤空時一次只能放一只水果供吃者取用,請用P、V原語實現爸爸、兒子、女兒三個並發進程的同步。
分析在本題中,爸爸、兒子、女兒共用一個盤子,盤中一次只能放一個水果。當盤子為空時,爸爸可將一個水果放入果盤中。若放入果盤中的是桔子,則允許兒子吃,女兒必須等待;若放入果盤中的是蘋果,則允許女兒吃,兒子必須等待。本題實際上是生產者-消費者問題的一種變形。這里,生產者放入緩沖區的產品有兩類,消費者也有兩類,每類消費者只消費其中固定的一類產品。
解:在本題中,應設置三個信號量S、So、Sa,信號量S表示盤子是否為空,其初值為l;信號量So表示盤中是否有桔子,其初值為0;信號量Sa表示盤中是否有蘋果,其初值為0。同步描述如下:
int S=1;
int Sa=0;
int So=0;
main()
{
cobegin
father(); /*父親進程*/
son(); /*兒子進程*/
daughter(); /*女兒進程*/
coend
}
father()
{
while(1)
{
P(S);
將水果放入盤中;
if(放入的是桔子)V(So);
else V(Sa);
}
}
son()
{
while(1)
{
P(So);
從盤中取出桔子;
V(S);
吃桔子;
}
}
daughter()
{
while(1)
{
P(Sa);
從盤中取出蘋果;
V(S);
吃蘋果;
}
}
思考題:
四個進程A、B、C、D都要讀一個共享文件F,系統允許多個進程同時讀文件F。但限制是進程A和進程C不能同時讀文件F,進程B和進程D也不能同時讀文件F。為了使這四個進程並發執行時能按系統要求使用文件,現用PV操作進行管理,請回答下面的問題:
(1)應定義的信號量及初值: 。
(2)在下列的程序中填上適當的P、V操作,以保證它們能正確並發工作:
A() B() C() D()
{ { { {
[1]; [3]; [5]; [7];
read F; read F; read F; read F;
[2]; [4]; [6]; [8];
} } } }
思考題解答:
(1)定義二個信號量S1、S2,初值均為1,即:S1=1,S2=1。其中進程A和C使用信號量S1,進程B和D使用信號量S2。
(2)從[1]到[8]分別為:P(S1) V(S1) P(S2) V(S2) P(S1) V(S1) P(S2) V(S2)
1、測量控制系統中的數據采集任務把所采集的數據送一單緩沖區;計算任務則 從該緩沖區中取出數據並進行計算。試寫出利用信號量機制實現兩者共享單緩沖區的同步算法。
Var Sempty,Sfull: semaphore:= 1,0
Begin
Parbegin
Collection:begin
repeat
采集一個數據;
wait(Sempty);
數據放入緩沖區;
signal(Sfull);
untill false;
end;
Compute:begin
repeat
wait(Sfull);
從緩沖區取出數據;
signal(Sempty);
計算;
` until false;
end;
Parend
End
2、有一閱覽室,共有100個座位。讀者進入時必須先在一種登記表上登記,該表為每一座位列一個表目,包括座號和讀者姓名。讀者離開時要注銷掉登記內容。試用wait和signal原語描述讀者進程的同步問題。
var mutex, readcount :semaphore := 1,100;
Begin
Parbegin
Process Reader:begin
repeat
wait(readcount);
wait(mutex);
<填入座號和姓名完成登記>;
signal(mutex);
<閱讀>
wait(mutex)
<刪除登記表中的相關表項,完成注銷>
signal(mutex);
signal(readcount);
until false;
end;
parend;
End;
1)、桌上有一空盤,只允許放一個水果,爸爸專向盤中放蘋果,媽媽專向盤中放桔子;女兒專吃盤中的蘋果,兒子專吃盤中的桔子;試用wait和signal原語實現爸爸、媽媽、女兒、兒子之間的同步問題。
var Sempty, Sapple, Sorange,: semaphore:= 1,0,0;
begin
parbegin
Father: begin
repeat
wait(Sempty); <put apple in tray>;
signal(Sapple); until false;
end;
Mother: begin
repeat
wait(Sempty); <put orange in tray>;
signal(Sorange); until false;
end;
Son: begin
repeat
wait(Sorange);
<take orange>;
signal(Sempty);
until false;
end;
Daughter: begin
repeat
wait(Sapple);
<take apple>;
signal(Sempty); until false;
end;
parend;
end;
1、在4×100米接力賽中,4個運動員之間存在如下關系,運動員1跑到終點把接力棒交給運動員2;運動員2一開始處於等待狀態,在接到運動員1傳來的接力棒后才能往前跑,他跑完100米后交給運動員3,運動員3也只有在接到運動員2傳來的棒后才能跑,他跑完100米后交給運動員4,運動員4接到棒后跑完全程。請試用信號量機制對其上過程進行分析。
var s1,s2,s3:semaphpre:=0,0,0;
begin
parbegin
Athlete1: begin
Run100m;
signal(s1);
end;
Athlete2: begin
wait(s1);
Run100m;
signal(s2);
end;
Athlete3: begin
wait(s2);
Run100m;
signal(s3);
end;
Athlete4: begin
wait(s3);
Run100m;
end;
parend;
end
2、在公共汽車上,司機和售票員各行其職,司機負責開車和到站停車;售票員負責售票和開、關車門;當售票員關好車門后駕駛員才能開車行駛。試用wait和signal操作實現司機和售票員的同步。
var s1,s2:semaphore:=0,0;
begin
parbegin
Process Driver
begin
repeat
<go right>;
<stop bus>;
signal(s2);
wait(s1);
until false;
end;
Process BookingClerk;
begin
repeat
<ticketing>;
wait(s2);
<open the door>;
<close the door>;
signal(s1);
until false
end;
parend;
end;
1、假設有3個並發進程P,Q,R,其中P負責從輸入設備上讀入信息,並傳送給Q,Q將信息加工后傳送給R,R負責打印輸出。進程P,Q共享一個有m個緩沖區組成的緩沖池;進程Q,R共享一個有n個緩沖區組成的緩沖池(假設緩沖池足夠大,進程間每次傳輸信息的單位均小於等於緩沖區長度),請寫出滿足上述條件的並發程序。(12分)
var mutex1,mutex2,Sip,Siq,Soq,Sor:semaphore:=1,1,m,0,n,0;
begin
parbegin
Process P
begin
repeat
<讀入信息>
wait(Sip);
wait(mutex1);
<數據放入緩沖區>
signal(mutex1);
signal(Siq);
until false
end;
Process Q
begin
repeat
wait(Siq);
wait(mutex1);
<從緩沖區中取出數據>
signal(mutex1);
signal(Sip);
<數據處理〉
wait(Soq);
wait(mutex2);
<處理后的數據放入緩沖區>
signal(mutex2);
signal(Sor);
until false
end;
Process R
repeat
wait(Sor);
wait(mutex2);
<把數據送入打印機完成打印>;
signal(mutex2);
signal(Soq);
until false
end
parend
end
2、有一只鐵籠子,每次只能放入一只動物,獵手向籠子里放入老虎,農民向籠子里放入豬;動物園等待取籠子里的老虎,飯店等待取籠子里的豬。現請用wait和signal操作寫出能同步執行的程序。
var Sempty, Stiger, Spig,: semaphore:= 1,0,0;
begin
parbegin
Hunter: begin
repeat
wait(Sempty);
<put tiger in cage>;
signal(Stiger);
until false;
end;
Farmer: begin
repeat
wait(Sempty);
<put pig in cage>;
signal(Spig);
until false;
end;
Zoo: begin
repeat
wait(Stiger);
<take tiger>;
signal(Sempty);
until false;
end;
Hotel: begin
repeat
wait(Spig);
<take pig>;
signal(Sempty); until false;
end;
parend;
end;