从最初接触PV操作,到有些许理解时候的赞叹,但是具体问题下,自己并不能做到顺畅的分解出问题中的进程与进程之间交织的关系。虽然作者在书中很清晰的总结了一些判断标准。
需要处理的关系只有两种:同步和互斥。
互斥的问题尤其简单,只是设置一个变量mutex,令其初值为1。每个进程进来前先P一下,表示想要用临界区,从临界区出来就要V一下,重置mutex为1。
对于同步问题也并不太难,只需要用变量表示同步进程中间传递的资源数目。这是一个有向图的模型,经常可以用一个看似复杂的有向图的前驱模型来显示PV操作的使用。最简单的便是:从A->B,假设A->B之间设置的信号量是a,那么:A结束后就要V(a),表示V一下a,B在启动之前,首先要P(a),表示P一下a.这样对一个变量的P、V分散在两个进程间,像是在茫茫人海,各执一个信物,表示一种微妙的联系,这个联系叫同步。
还是拿经典的生产者–消费者问题来深入解释。
在生产者–消费者问题中,很容易理解的是对缓冲区的互斥访问,我们设置一个信号量mutex = 1,表示初始可用数为1.
对我来说,我还能体会到第一次看到设置两个信号量:full和empty分别表示空闲缓冲区和满缓冲区的时候,很不理解。
总是以为:full和empty是可以相互推导的关系,何必要设置两个呢?
事实上,这个认识直到现在才真的被打破。我一直忽略了,这里设置两个变量的出发点是我们刚说的A->B这个例子。
这里假设生产者进程是A,消费者进程是B,那么A和B之间的关系除了互斥访问缓冲区外,还有两个同步关系:A->B 和 B->A。
因此必须有两个信号量来表现这两个关系。
那么这两个信号量是什么呢?
A->B ,这里生产者进程结束后,V的是什么呢?V的是生产的东西对吧!所以设计一个full表示缓冲区中已经有的东西,刚好到B准备启动的时候,先P一个full。
同理,B->A,消费者进程消费结束后,需要V一个什么?V的是它用过的空壳子吧!所以设计一个empty来表示缓冲区的空白,在A启动时,先P一下empty。
由此分析,问题就很明确了。
但是或许你会问,怎么推出来的有两道同步关系呢?
既然A和B是面对缓冲区进行的,并没有直接接触,哪里来的同步关系?
这个问题需要看的角度应该是这样的:如果A和B之间没有这两道同步关系,A不管缓冲区空不空,直接生产,B也不管缓冲区有没有东西,直接消费,这样没有限制,一定是混乱的。有一种原则在告诉我们,如果A和B想要进行下去一定是互相牵制的。缓冲区不过是两者之间的一个媒介罢了,真正的关系还是A和B之间。
semaphore mutex = 1;
semaphore empty = n;
semaphore full = 0;
producer()
{
while(1)
{
生产一个;
P(empty);
P(mutex);
放到缓冲区; //访问临界区
V(mutex);
V(full);
}
}
consumer()
{
while(1)
{
P(full);
P(mutex);
从缓冲区取出一个; //访问临界区
V(mutex);
V(empty);
}
}
以上。