写在前面:
在这一篇博客,我们将学习经典的进程同步问题,较有代表性的是“生产者—消费者”问题、“读者—写者”问题、“哲学家进餐”问题,通过对这些问题的研究和学习,可以帮助我们更好地理解进程同步的概念及实现方法。
生产者-消费者问题:
生产者-消费者(producer-consumer)问题是一个著名的进程同步问题。它描述的是:有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费。为使生产者进程与消费者进程能并发执行,在两者之间设置了一个具有 n 个缓冲区的缓冲池,生产者进程将它所生产的产品放入一个缓冲区中;消费者进程可从一个缓冲区中取走产品去消费。尽管所有的生产者进程和消费者进程都是以异步方式运行的,但它们之间必须保持同步,即不允许消费者进程到一个空缓冲区去取产品,也不允许生产者进程向一个已装满产品且尚未被取走的缓冲区中投放产品。
可利用一个数组来表示上述的具有 n 个(0,1,…,n-1)缓冲区的缓冲池。用输入指针 in 来指示下一个可投放产品的缓冲区,每当生产者进程生产并投放一个产品后,输入指针加 1;用一个输出指针 out 来指示下一个可从中获取产品的缓冲区,每当消费者进程取走一个产品后,输出指针加 1。
由于这里的缓冲池是组织成循环缓冲的,故应把输入指针加1 表示成 in:= (in+1)mod n; 输出指针加 1 表示成 out:= (out+1) mod n。当 (in+1) mod n=out时表示缓冲池满;而 in=out 则表示缓冲池空。
此外,还引入了一个整型变量 counter,其初始值为 0。每当生产者进程向缓冲池中投放一个产品后,使 counter 加 1;反之,每当消费者进程从中取走一个产品时,使 counter 减 1。
在生产者进程中使用一局部变量 nextp,用于暂时存放每次刚生产出来的产品;而在消费者进程中,则使用一个局部变量 nextc,用于存放每次要消费的产品。
由于生产者—消费者问题是相互合作的进程关系的一种抽象,例如,在输入时,输入进程是生产者,计算进程是消费者;而在输出时,计算进程是生产者,而打印进程是消费者。
利用记录型信号量解决生产者—消费者问题:
假定在生产者和消费者之间的公用缓冲池中,具有 n 个缓冲区,这时可利用互斥信号量 mutex 实现诸进程对缓冲池的互斥使用。利用资源信号量 empty 和 full 分别表示缓冲池中空缓冲区和满缓冲区的数量,这两个信号量是用来同步进程的。那么当生产一个资源成功,empty-1;
又假定这些生产者和消费者相互等效,只要缓冲池未满,生产者便可将消息送入缓冲池;只要缓冲池未空,消费者便可从缓冲池中取走一个消息。
对生产者—消费者问题可描述如下:
Var mutex: semaphore:=1;
empty: semaphore:=n;
full: semaphore:0;
buffer:array[0,…,n-1] of item;
in: integer:=0;
out: integer:=0;
begin
parbegin
proceducer: begin
repeat
......
producer an item nextp; //生产下一个产品nextp
......
wait(empty); //申请一个空缓冲区,申请成功才能往里放产品,empty-1
wait(mutex); //判断有没有消费者进程往buffer里拿产品
buffer(in):=nextp; //将产品nextp放入下标为in的缓冲区中去
in:=(in+1) mod n; //缓冲池是组织成循环缓冲的,这里对n取余是为循环
signal(mutex); //释放缓冲池,可以让消费者进程从buffer取产品了
signal(full); //由于放产品成功,所以满缓冲区多了一个,故而释放