6) 综合应用:生产者消费者问题、读者和写者问题、哲学家进餐问题等。
2.4.1 生产者—消费者问题
1 .利用记录型信号量解决生产者— 消费者问题
假定在生产者和消费者之间的公用缓冲池中,具有n个缓冲区,这时可利用互斥信号量mutex实现诸进程对缓冲池的互斥使用。利用信号量empty和full分别表示缓冲池中空缓冲区和满缓冲区的数量。又假定这些生产者和消费者相互等效,只要缓冲池未满,生产者便可将消息送入缓冲池;只要缓冲池未空,消费者便可从缓冲池中取走一个消息。对生产者—消费者问题可描述如下:
Var mutex,empty,full: semaphore:=1,n,0;
buffer:array[0,…,n-1] of item;
in,out: integer:=0,0;
begin
parbegin
proceducer: begin
repeat
producer an item nextp;
wait(empty);
wait(mutex);
buffer(in):=nextp;
in:=(in+1) mod n;
signal(mutex);
signal(full);
until false;
end
consumer: begin
repeat
wait(full);
wait(mutex);
nextc:=buffer(out);
out:=(out+1) mod n;
signal(mutex);
signal(empty);
consumer the item in nextc;
until false;
end
parend
end
在生产者—消费者问题中应注意:首先,在每个程序中用于实现互斥的wait(mutex)和signal(mutex)必须成对地出现;其次,对资源信号量empty和full的wait和signal操作,同样需要成对地出现,但它们分别处于不同的程序中。例如,wait(empty)在计算进程中,而signal(empty)则在打印进程中,计算进程若因执行wait(empty)而阻塞,则以后将由打印进程将它唤醒;最后,在每个程序中的多个wait操作顺序不能颠倒,应先执行对资源信号量的wait操作,然后再执行对互斥信号量的wait操作,否则可能引起进程死锁。
2 .利用AND 信号量解决生产者— 消费者问题
对于生产者—消费者问题,也可利用AND信号量来解决,即用Swait(empty,mutex)来代替wait(empty)和wait(mutex);用Ssignal(mutex,full)来代替signal(mutex)和signal(full);用Swait(full,mutex)来代替wait(full)和wait(mutex),以及用Ssignal(mutex,empty)代替Signal(mutex)和Signal(empty)。利用AND信号量来解决生产者—消费者问题的算法描述如下:
Var mutex,empty,full: semaphore:=1,n,0;
buffer:array[0,…,n-1] of item;
in out: integer:=0,0;
begin
parbegin
producer: begin
repeat
produce an item in nextp;
Swait(empty,mutex);
buffer(in):=nextp;
in:=(in+1)mod n;
Ssignal(mutex,full);
until false;
end
consumer:begin
repeat
Swait(full,mutex);
Nextc:=buffer(out);
Out:=(out+1) mod n;
Ssignal(mutex,empty);
consumer the item in nextc;
until false;
end
parend
end
3 .利用管程解决生产者— 消费者问题
在利用管程方法来解决生产者—消费者问题时,首先便是
为它们建立一个管程,并命名为ProclucerConsumer,或简称
为PC。其中包括两个过程:
(1) put(item)过程。生产者利用该过程将自己生产的产品
投放到缓冲池中,并用整型变量count来表示在缓冲池中已有
的产品数目,当count≥n时,表示缓冲池已满,生产者须等待。
(2) get(item)过程。消费者利用该过程从缓冲池中取出一
个产品,当count≤0时,表示缓冲池中已无可取用的产品,消
费者应等待。
PC管程可描述如下:
type producer-consumer=monitor
Var in,out,count: integer;
buffer: array[0, …, n-1] of item;
notfull,notempty:condition;
procedure entry put(item)
begin
if count>=n then notfull.wait;
buffer(in):=nextp;
in:=(in+1) mod n;
count:=count+1;
if notempty.queue then notempty.signal;
end
procedure entry get(item)
begin
if count<=0 then notempty.wait;
nextc:=buffer(out);
out:=(out+1) mod n;
count:=count-1;
if notfull.quene then notfull.signal;
end
begin in:=out:=0;
count:=0
end
在利用管程解决生产者—消费者问题时,其中的生产者和消
费者可描述为:
producer: begin
repeat
produce an item in nextp;
PC.put(item);
until false;
end
consumer: begin
repeat
PC.get(item);
consume the item in nextc;
until false;
end
2.4.2 哲学家进餐问题
1 .利用记录型信号量解决哲学家进餐问题
经分析可知,放在桌子上的筷子是临界资源,在一段时间内只允许一位哲学家使用。为了实现对筷子的互斥使用,可以用一个信号量表示一只筷子,由这五个信号量构成信号量数组。其描述如下:
Var chopstick: array[0,…,4] of semaphore;
所有信号量均被初始化为1,第i位哲学家的活动可描述为:
repeat
wait(chopstick[i]);
wait(chopstick[(i+1)mod 5]);
eat;
signal(chopstick[i]);
signal(chopstick[(i+1)mod 5]);
think;
until false;
在以上描述中,当哲学家饥饿时,总是先去拿他左边的筷子,即执行wait(chopstick[i]); 成功后,再去拿他右边的筷子,即执行wait(chopstick[(i+1)mod 5]);又成功后便可进餐。进餐完毕,又先放下他左边的筷子,然后再放右边的筷子。虽然,上述解法可保证不会有两个相邻的哲学家同时进餐,但有可能引起死锁。假如五位哲学家同时饥饿而各自拿起左边的筷子时,就会使五个信号量chopstick均为0; 当他们再试图去拿右边的筷子时,都将因无筷子可拿而无限期地等待。对于这样的死锁问题,可采取以下几种解决方法:
(1) 至多只允许有四位哲学家同时去拿左边的筷子,最终能保证至少有一位哲学家能够进餐,并在用毕时能释放出他用过的两只筷子,从而使更多的哲学家能够进餐。
(2) 仅当哲学家的左、右两只筷子均可用时,才允许他拿起筷子进餐。
(3) 规定奇数号哲学家先拿他左边的筷子,然后再去拿右边的筷子,而偶数号哲学家则相反。按此规定,将是1、2号哲学家竞争1号筷子;3、4号哲学家竞争3号筷子。即五位哲学家都先竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会有一位哲学家能获得两只筷子而进餐。
2 .利用AND 信号量机制解决哲学家进餐问题
在哲学家进餐问题中,要求每个哲学家先获得两个临界资源(筷子)后方能进餐,这在本质上就是前面所介绍的AND同步问题,故用AND信号量机制可获得最简洁的解法。描述如下:
Var chopsiick array of semaphore:=(1,1,1,1,1);
processi
repeat
think;
Sswait(chopstick[(i+1)mod 5],chopstick[i]);
eat;
Ssignat(chopstick[(i+1)mod 5],chopstick[i]);
until false;
2.4.3 读者—写者问题
1 .利用记录型信号量解决读者— 写者问题
为实现Reader与Writer进程间在读或写时的互斥而设置了一个互斥信号量Wmutex。另外,再设置一个整型变量Readcount表示正在读的进程数目。由于只要有一个Reader进程在读,便不允许Writer进程去写。因此,仅当Readcount=0,表示尚无Reader进程在读时,Reader进程才需要执行Wait(Wmutex)操作。若Wait(Wmutex)操作成功,Reader进程便可去读,相应地,做Readcount+1操作。同理,仅当Reader进程在执行了Readcount减1操作后其值为0时,才须执行signal(Wmutex)操作,以便让Writer进程写。又因为Readcount是一个可被多个Reader进程访问的临界资源,因此,也应该为它设置一个互斥信号量rmutex。
读者—写者问题可描述如下:
Var rmutex,wmutex: semaphore:=1,1;
Readcount: integer:=0;
begin
parbegin
Reader: begin
repeat
wait(rmutex);
if readcount=0 then wait(wmutex);
Readcount:=Readcount+1;
signal(rmutex);
perform read operation;
wait(rmutex);
readcount:=readcount-1;
if readcount=0 then signal(wmutex);
signal(rmutex);
until false;
end
writer: begin
repeat
wait(wmutex);
perform write operation;
signal(wmutex);
until false;
end
parend
end
2 .利用信号量集机制解决读者— 写者问题
这里的读者—写者问题与前面的略有不同,它增加了一个限制,即最多只允许RN个读者同时读。为此,又引入了一个信号量L,并赋予其初值为RN,通过执行wait(L,1,1)操作,来控制读者的数目。每当有一个读者进入时,就要先执行wait(L,1,1)操作,使L的值减1。当有RN个读者进入读后,L便减为0,第RN+1个读者要进入读时,必然会因wait(L,1,1)操作失败而阻塞。对利用信号量集来解决读者—写者问题的描述如下:
Var RN integer;
L,mx: semaphore:=RN,1;
begin
parbegin
reader: begin
repeat
Swait(L,1,1);
Swait(mx,1,0);
perform read operation;
Ssignal(L,1);
until false;
end
writer: begin
repeat
Swait(mx,1,1;L,RN,0);
perform write operation;
Ssignal(mx,1);
until false;
end
parend
end
其中,Swait(mx,1,0)语句起着开关的作用。只要无writer进程进入写,mx=1,reader进程就都可以进入读。但只要一旦有writer进程进入写时,其mx=0,则任何reader进程就都无法进入读。Swait(mx,1,1;L,RN,0)语句表示仅当既无writer进程在写(mx=1),又无reader进程在读(L=RN)时,writer进程才能进入临界区写。