(操作系統原理·第三章)生產者-消費者問題

生產者--消費者問題:

有n 個生產者和m 個消費者,連接在一個有k 個單位緩衝區的有界緩衝上,故又叫有界緩衝問題。其中,pi 和cj 都是併發進程,只要

緩衝區未滿,生產者pi 生產的產品就可投入緩衝區;類似地,只要緩衝區不空,消費者進程cj 就可從緩衝區取走並消耗產品。

算法表述:

var k:integer;
type item:any;
buffer:array[0..k-1] of item;
in,out:integer:=0;
counter:integer:=0;
process producer
while (TRUE) /* 無限循環*/
produce an item in nextp; /* 生產一個產品*/
if (counter==k) sleep( ); /* 緩衝滿時,生產者睡眠*/
buffer[in]:=nextp; /* 將一個產品放入緩衝區*/
in:=(in+1) mod k; /* 指針推進*/
counter:=counter+1; /* 緩衝內產品數加1*/
if (counter==1) wakeup( consumer); /* 緩衝爲空了,加進一件產品並喚醒消費者*/
process consumer
while (TRUE) /* 無限循環*/
if (counter==0) sleep ( ); /* 緩衝區空,消費者睡眠*/
nextc:=buffer[out]; /* 取一個產品到nextc*/
out:=(out+1) mod k; /* 指針推進*/
counter:=counter-1; /* 取走一個產品,計數減1*/
if (counter==k-1) wakeup( producer); /* 緩衝滿了,取走一件產品並喚醒生產者*/
consume thr item in nextc; /* 消耗產品*/

其中,假如一般的高級語言都有sleep( )和wakeup( )這樣的系統調用。從上面的程序可以看出,算法是正確的,兩組進程順序執行結果也正確。但若併發執行,就會出現錯誤結果,出錯的根子在於進程之間共享了變量counter,對counter 的訪問未加限制。生產者和消費者進程對counter 的交替執行會使其結果不唯一。例如,counter 當前值爲8,如果生產者生產了一件產品,投入緩衝區,擬做counter 加1 操作。同時消費者獲取一個產品消費,擬做counter 減1 操作。假如兩者交替執行加或減1 操作,取決於它們的進行速度,counter 的值可能是9,也可能是7,正確值應爲8。

更爲嚴重的是生產者和消費者進程的交替執行會導致進程永遠等待,造成系統死鎖。假定消費者讀取counter 發現它爲0。此時調度程序暫停消費者讓生產者運行,生產者加入一個產品,將counter 加1,現在counter 等於1 了。它想當然地推想由於counter剛剛爲0,所以,此時消費者一定在睡眠,於是生產者調用wakeup 來喚醒消費者。不幸的是,消費者還未去睡覺,喚醒信號被丟失掉。當消費者下次運行時,因已測到counter爲0,於是去睡眠。這樣生產者遲早會填滿緩衝區,然後,去睡覺,形成了進程都永遠處於睡眠狀態。
出現不正確結果不是因爲併發進程共享了緩衝區,而是因爲它們訪問緩衝區的速率不匹配,或者說pi,cj 的相對速度不協調,需要調整併發進程的進行速度。併發進程間的這種制約關係稱進程同步,交互的併發進程之間通過交換信號或消息來達到調整相互速率,保證進程協調運行的目的。


解決方法:用記錄型信號量實現互斥

Var A : ARRAY[1..m] of integer;
mutex : semaphore;
mutex:= 1;
cobegin
process Pi
var Xi:integer;
begin
L1:
按旅客定票要求找到A[j];
P(mutex);
Xi := A[j];
if Xi>=1
then begin
Xi:=Xi-1;A[j]:=Xi;
V(mutex);{輸出一張票};
end;
else begin
V(mutex);{輸出“票已售完”};
end;
goto L1;
end;
coend.


Var A : ARRAY[1..m] of integer;
s : ARRAY[1..m] of semaphore;
s[j] := 1;
cobegin
process Pi
var Xi:integer;
begin
L1:
按旅客定票要求找到A[j];
P(s[j]);
Xi := A[j];
if Xi>=1
then begin
Xi:=Xi-1;A[j]:=Xi;
V(s[j]);{輸出一張票};
end;
else begin
V(s[j]);{輸出“票已售完”};
end;
goto L1;
end;
coend.

左面的程序引入一個信號量mutex,用於管理票源數據,其初值爲l。假設進程Tl首先調用P 操作,則P 操作將把信號量mutex 減l,T1 進入臨界區;此時,若進程T2也想進入臨界區而調用P 操作,那麼,P 操作便會阻塞T2 並使它等待mutex。當T1離開臨界區時,它調用V 操作,V 操作將喚醒等待mutex 的進程T2;於是T2 就可進入臨界區執行。事實上,當進程T1 和T2 只有同時買一個航班的機票時纔會發生與時間有關的錯誤,因此,臨界區應該是與A[j]有關的,所以,可以對左面的程序進行改進,引入一組信號量s[j],從而,得到了右面的程序,不難看出,它提高了進程的併發程度。

要提醒注意的是:任何粗心地使用P、V 操作會違反臨界區的管理要求。如忽略了else 部分的V 操作,將致使進程在臨界區中判到條件不成立時無法退出臨界區,而違反了對臨界區的管理要求。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章