生产者进程和消费者进程都以异步方式运行,但它们之间必须保持同步。
同步模式:生产者和消费者之间的关系
互斥模式:不同生产者之间的关系、不同消费者之间的关系
①利用记录型信号量解决生产者–消费者问题
可利用互斥信号量mutex实现诸进程对缓冲池的互斥使用;
利用信号量empty和full分别表示缓冲池中空缓冲池和满缓冲池的数量。
//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
producer://生产者
begin
repeat
...
生产一个产品放入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);
//临界区
mextc := buffer(out);
out := (out+1) mod n;
//退出区
signal(mutex);
signal(empty);
//剩余区
消费nextc中的产品;
until false;
end
parend
end
注意:
1)每个程序中用于实现互斥的wait(mutex)
和signal(mutex)
必须成对出现。
2)对资源信号量empty
和full
的wait
和signal
操作,同样需要成对出现,但处于不同的程序中。
3)在每个程序中的多个wait
操作顺序不颠倒。应先先申请资源信号量,后申请互斥信号量,否则可能引起进程死锁。siganl
可以没有顺序。
②利用AND信号量解决生产者–消费者问题
//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
producer://生产者
begin
repeat
...
生产一个产品放入nextp;
...
//进入区
Swait(empty, mutex);//不必考虑信号量先后问题
//临界区
buffer(in) := nextp;
in := (in+1) mod n;
//退出区
Ssignal(mutex, full);
until false;
end
consumer://消费者
begin
repeat
//进入区
Swait(full, mutex);//不必考虑信号量先后问题
//临界区
mextc := buffer(out);
out := (out+1) mod n;
//退出区
Ssignal(mutex, empty);
//剩余区
消费nextc中的产品;
until false;
end
parend
end
五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在桌子上有五只碗和五支筷子,他们的生方式是交替地进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐毕,放下筷子继续思考。
可见:
相邻两位不能同时进餐;
最多只能有两人同时进餐。
①利用记录型信号量解决哲学家时餐问题
放在桌子的筷子是临界资源,在一段时间内只允许一个哲学家使用。为实现对筷子的互斥使用,用一个信号量表示一只筷子,五个信号量构成信号量数组。
Var chopstick: array[0, ..., 4] of semaphore := 1
所有信号量均被初始化为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;
以上算法虽能保证相邻两位不会同时进餐,但可能引起死锁。
例如五拉哲学家同时饥饿而各自拿起左边的筷子时,就会使五个信号量chopstick均为0,都将因无筷子可拿而无限等待。
解决办法:
1)同时最多允许4位哲学家拿左边筷子(能够保证至少一位哲学家能够进餐)。
Var chopstick: array[0, ..., 4] of semaphore := 1;
var count: semaphore := 4;//资源信号量
repeat
//进入区
wait(count);
wait(chopstick[i]);
wait(chopstick[(i+1) mod 5]);
//临界区
...
eat;
...
//退出区
signal(chopstick[i]);
signal(chopstick[(i+1) mod 5]);
signal(count);
//剩余区
...
think;
until false;
2)哲学家必须拿到两支筷子(使用AND信号量),否则释放已拿到的筷子。
详见下方②
3)奇数号哲学家先拿左边筷子,再拿右边筷子;偶数号哲学家先拿右边筷子,再拿左边筷子。
Var chopstick: array[0, ..., 4] of semaphore := 1;
Var i: integer;
repeat
//进入区
if i mod 2=1 then
begin
wait(chopstick[i]);//先拿左边筷子
wait(chopstick[(i+1) mod 5]);//再拿右边筷子
end
else
begin
wait(chopstick[(i+1) mod 5]);//先拿右边筷子
wait(chopstick[i]);//再拿左边筷子
end
end
//临界区
eat;
//退出区
signal(chopstick[i]);
signal(chopstick[(i+1) mod 5]);
//剩余区
...
think;
until false;
②利用AND信号量机制解决哲学家进餐问题
在哲学家时餐问题中,要求每个哲学家先获得两个临界资源(筷子)后方能进餐。本质上是AND同步问题。
Var chopstick: array[0, ..., 4] of semaphore := (1, 1, 1, 1, 1);//表示chopstick[i]可用
Philosopher i;
repeat
think;
//进入区
Swait(chopstick[(i+1) mod 5], chopstick[i]);
//临界区
eat;
//退出区
Ssignal(chopstick[(i+1) mod 5], chopstick[i]);
until false;
一个数据文件或记录可被多个进程共享。
1)只要求读文件的进程为“Reader进程”,其它进程则称为“Writer进程”。
2)允许多个进程同进读一个共享对象,但不允许一个Writer进程和其他Reader进程或Writer进程同时访问共享对象。
“读者–写者问题”是保证一个Writer进程必须与其他进程互斥地访问共享对象的同步问题。
读–读共享;写–写互斥;写–读互斥。
①利用记录型信号量解决读者–写者问题
互斥信号量wmutex:实现Reader与Writer进程间在读或写时的互斥。
互斥信号量rmutex:实现Reader进程间互斥访问Readcount的信号量。
整型变量Readcount:表示正在读的进程数目。
由于只有一个Reader进程在读,便不允许Writer进程写。所以仅当Readcount=0(即无Reader进程在读)时,Reader才需要执行Wait(wmutex)操作。若Wait(wmutex)操作成功,Reader进程便可去读,相应地,做Readcount+1操作。
同理,仅当Reader进程在执行了Readcount-1=0时,才需要执行signal(wmutex)操作,以便让Writer进程写。
//Wmutex:读-写互斥;写-写互斥
//Rmutex:读间访问Readcount互斥
//Readcount:记录记者进程数
Var wmutex, rmutex: semaphore := 1, 1;
Readcount: integer := 0;
begin
parbegin
Reader:begin//读进程
repeat
//进入区
wait(rmutex);//子进入区
if Readcount=0 then wait(wmutex);
Readcount := Readcount + 1;//子临界区
signal(rmutex);//子退出区
//临界区
...
读;
...
//退出区
wait(rmutex);
Readcount := Readcount - 1;
if Readcount=0 then signal(wmutex);
signal(rmutex);
until false;
end
Writer:begin//写进程
repeat
wait(wnutex);
写;
signal(wnutex);
until false;
end
parend
end
评价:能实现读者–写者问题,但读优先,对写者不公平。
②利用信号量集机制实现读者–写者问题
引入信号量L,并赋予其初值RN,通过执行Swait(L, 1, 1)操作,来控制读者的数目(即最多允许RN个读者同时读)。
每当有一个读者进入时,就要先执行Swait(L, 1, 1)操作,使L的值减1。当有RN个读者进入读后,L便减为0,第RN+1个读者要进入读时,必然会因Swait(L, 1, 1)操作失败而阻塞。
//L:控制读者进程的数目<=RN
//Mx:实现读-写互斥;写-写互斥
Var RN: integer;
L, mx: semaphore : RN, 1;
begin
parbegin
reader:begin
repeat
Swait(L, 1, 1);
Swait(mx, 1, 0);//a
...
读;
...
Ssignal(L, 1);
until false;
end
writer:begin
repeat
Swait(mx, 1, 1; L, RN, 0);//b
写;
Ssignal(mx, 1);
until false;
end
parend
end
注释a
Swait(mx, 1, 0)语句起着开关的作用。只要无writer进程进入写,即mx=1,reader进程就可以进入读。但只要一旦有writer进程进入写时,即mx=0,则任何reader进程就都无法进入读。注释b
Swait(mx, 1, 1; L, RN, 0)语句表示仅当既无writer进程在写(即mx=1),又无reader进程在读(即L=RN),writer进程才能进入临界区写。