本節以兩個進程P0和P1對同一個臨界區訪問為例,討論臨界區問題的軟件解決方案。
begin
COBEGIN
P0:
P1:
COEND
end
[方法1]設置一個公用整型變量turn,用於指示被允許進入臨界區的進程的編號,算法如下:
進程p0
…
while turn≠0 do skip;
critical section
turn:=1;
…
進程p1
…
while turn≠1 do skip;
critical section
turn:=0;
…
該方法可確保只允許一個進程進入臨界區。但是,強制兩個進程輪流地進入臨界區,很容易造成資源利用不充分。例如,當進程p0退出臨界區后將turn置為1,以便允許P1進入臨界區。但如果進程P1暫時並未要求訪問該臨界資源,而P0又想再次訪問該資源時就無法進入臨界區。可見,此算法不能保證實現“空閑讓進”的原則。
[方法2]為避免強制進程P0和P1輪流訪問臨界區,可以設置兩個標志位flag[0]和flag[1],每個進程去檢測對方的訪問標志。算法如下:
進程p0
…
while flag[1] do skip;
flag[0]:=true;
critical section
flag[0]:=false;
…
進程p1
…
while flag[0] do skip;
flag[1]:=true;
critical section
flag[1]:=false;
…
該算法的思想是在進程訪問臨界區之前,先去查看臨界區是否正被占用。如果已經被占用,該進程需等待;否則才進入臨界區。為此,設置了一個數組,使其中每個元素的初值為false,表示所有進程都未進入臨界區。
算法雖然解決了空閑讓進的問題。但是會出現這樣的問題,當P0和P1都未進入臨界區時,flag[0]和flag[1]的值都為false。如果P0和P1幾乎同時要求進入臨界區,它們可能會發現對方的訪問標志都為false,即出現如下執行序列,
P0執行while語句發現flag[1]的值是false,
P1執行while語句發現flag[0]的值是false,
P0把flag[0]設為true並且進入臨界區,
P1把flag[1]設為true並且進入臨界區.
顯然,兩個進程都進入臨界區違背了忙則等待的准則。
[方法3]由於方法2的問題在於:進程Pi可以在其它進程觀察到flag[i]之后,進入臨界區之前修改標志flag[i],因此產生與時間有關的錯誤。我們通過交換測試與設置的次序來解決這個問題。
進程p0
…
flag[0]:=true;
while flag[1] do skip;
critical section
flag[0]:=false;
…
進程p1
…
flag[1]:=true;
while flag[0] do skip;
critical section
flag[1]:=false;
…
該算法可以有效地防止兩個進程同時進入臨界區。但仔細分析又可看出,該算法又會造成最終誰都不能進入臨界區的后果。既違背了“有空讓進”的准則,又違背了“有限等待”的准則。例如,當P0和P1幾乎在同時都因要進入臨界區,而分別把自己的標志flag置為true后,又都立即去檢查對方的標志,因而都發現對方也要進入臨界區,即對方的標志也為true,於是雙方都在“謙讓”,結果誰也進不了臨界區。
[方法4]當進程要進入臨界區並發現其它進程也希望進入臨界區時,就主動把自己的標志置成false,以便讓其它進程優先進入,過一會兒再置成true,表示希望進入true,於是雙方都在“謙讓”,結果誰也進不了臨界區。算法如下:
進程p0
…
flag[0]:=true;
while flag[1] do
begin
flag[0]:=false;
延遲一小段時間
flag[0]:=true;
end;
critical section
flag[0]:=false;
…
進程p1
…
flag[1]:=true;
while flag[0] do
begin
flag[1]:=false;
延遲一小段時間
flag[1]:=true;
end;
critical section
flag[1]:=false;
…
該算法已經接近真解,但是如果按下列次序執行仍然會出現相互阻塞的問題。
P0把flag[0]設為true,
P1把flag[1]設為true,
P0查看標志flag[1],
P1查看標志flag[0],
P0把flag[0]設為false,
P1把flag[1]設為false,
P0把flag[0]設為true,
P1把flag[1]設為true,
雖然沒有死鎖,但是各進程按這樣的速度和順序執行下去永遠不會進入臨界區。
[Dekker算法]該算法是結合了方法1和方法3中的關鍵概念而形成的。它為每個進程設置了相應的標志位flag[i],當flag[i]=true時表示進程Pi要求進入臨界區,或正在臨界區中執行。此外,還設置了一個turn變量,用於指示允許進入臨界區的進程編號。進程為了進入臨界區先置flag[i]為true並置turn為j,表示如果進程j想進入臨界區就讓進程j優先進入。既保證互斥使用臨界區,又不會出現互相謙讓的局面。算法描述如下:
VAR flag:array[0..1] of Boolean;
turn:0..1;
PROCEDURE P0
BEGIN
repeat
flag[0]:=true;
while flag[1] do if turn=1 then
BEGIN
flag[0]:=false;
while turn=1 do skip;
flag[0]:=true;
END;
critical section
turn:=1;
flag[0]:=false;
forever
END;
PROCEDURE P1
BEGIN
repeat
flag[1]:=true;
while flag[0] do if turn=0 then
BEGIN
flag[1]:=false;
while turn=0 do skip;
flag[1]:=true;
END;
critical section
turn:=0;
flag[1]:=false;
forever
END;
BEGIN
flag[0]:=false;
flag[1]:=false;
turn:=1;
COBEGIN
P0;P1;
COEDN;
END;
[Perterson算法]Dekker算法雖然解決了臨界區的訪問問題,但是,算法過於復雜,也不利於直接證明。Perterson於1981年提出一種簡單的算法:
VAR flag:array[0..1] of Boolean;
turn:0..1;
PROCEDURE P0
BEGIN
repeat
flag[0]:=true;
turn:=1;
while flag[1] and turn=1 do skip;
critical section
flag[0]:=false;
forever
END;
PROCEDURE P1
BEGIN
repeat
flag[1]:=true;
turn:=0;
while flag[0] and turn=0 do skip;
critical section
flag[1]:=false;
forever
END;
BEGIN
flag[0]:=false;
flag[1]:=false;
turn:=1;
COBEGIN
P0;P1;
COEND;
END;
Perterson算法能容易地推廣到解決n個進程訪問臨界區的問題。
利用軟件方法來解決諸進程互斥進入臨界區的問題會破壞程序的可讀性,有些機器采用提供特定的硬件指令的辦法來實現。多個進程並行執行時表現為斷斷續續的運行過程,在對一個變量查看和修改之間可能被中斷,由於一條硬件指令在執行過程中是不可中斷的,所以,把查看和修改動作用一個指令來完成可以避免上述錯誤。能夠用於解決臨界區問題的指令有測試與設置(test-set)和交換(swap)等指令。
一、利用Test-and-set指令管理臨界區
1.Test-and-set指令
在許多機器中都有這樣的指令,不同機器的相應指令的功能是相同的,因而我們不局限於某特定的機器去定義Test-and-set指令。
FUNCTION TS(var lock:boolean):boolean;
BEGIN
TS:=lock;
Lock:=true;
END
其中,lock有兩種狀態,當lock=false時,表示該資源空閑;當lock=true時,表示該資源正在被使用。
2.利用TS管理臨界區
為了實現各個進程對臨界資源的互斥訪問,可為每個臨界資源設置一個布爾變量lock,並賦予其初值為false。用TS指令讀取變量lock的狀態並將true賦予lock,這相當於關閉了臨界區,使其它進程不能進入臨界區。利用TS指令實現互斥的循環進程可描述為:
repeat
…
while TS(lock) do skip;
critical section
lock:=false;
…
until false;
程序中的第一條語句用於檢查TS指令執行后的TS狀態。若為false表示資源空閑,進程可進入臨界區;否則,不斷測試執行TS指令后的TS變量值,直至其為false。當進程退出臨界區時,設置變量lock為false,以允許其它進程進入臨界區。
二、利用swap指令管理臨界區
1.Swap指令
該指令稱為交換指令。在微型機中該指令又稱為XCHG 指令,用於交換兩個字的內容,具體描述如下:
PROCEDURE Swap(var a,b:Boolean)
VAR temp:Boolean;
BEGIN
temp:=a;
a:=b;
b:=temp;
END
2.利用Swap管理臨界區
在利用Swap實現進程互斥時,可為臨界資源設置一個全局布爾變量lock,其初值為false,在每個進程中再利用一個局部布爾變量key。利用Swap指令實現進程互斥的循環進程可描述為:
repeat
…
key:=true;
repeat
Swap(lock,key);
Until key=false;
critical section;
lock:=false;
…
until false;