(转)临界区互斥方法的软件和硬件实现
本节以两个进程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;