Ref:
https://blog.csdn.net/fuzhongmin05/article/details/54616344
code:
无法避免竞争
对count的访问没有做限制。
这里有可能出现竞争条件,其原因是对count的访问未作限制。有可能出现以下情况:缓冲区为空,消费者刚刚读取count的值发现它为0,此时调度程序决定暂停消费者并启动运行生产者。生产者向缓冲区加入一个数据项,count加1。现在count的值变成了1,它推断认为count刚才为0,所以消费者此时一定在睡眠,于是生产者调用wakeup来唤醒消费者。
但是消费者在逻辑上并未睡眠,所以wakeup信号丢失,当消费者下次运行时,它将测试先前读取的count值,发现它为0。于是睡眠,生产者迟早会填满整个缓冲区,然后睡眠,这样一来,两个进程将永远睡眠下去。
引入信号量的操作
Dijkstra建议设立两种操作:down和up(分别为一般化后的sleep和wakeup)。对一信号量执行down操作,则是检查其值是否大于0。若该值大于0,则将其减1(即用掉一个保存的唤醒信号)并继续;若该值为0,则进程将睡眠,而且此时down操作并未结束。检查数值、修改变量值以及可能发生的睡眠操作均作为一个单一的、不可分割的原子操作完成。保证一旦一个信号量操作开始,则在该操作完成或阻塞之前,其他进程均不允许访问该信号量。这种原子性对于解决同步问题和避免竞争条件是绝对必要的。所谓原子操作,是指一组相关联的操作要么都不间断地执行,要么不执行。
up操作对信号量的值增1。如果一个或多个进程在该信号量上睡眠,无法完成一个先前的down操作,则由系统选择其中的一个(如随机挑选)并允许该进程完成它的down操作。于是,对一个有进程在其上睡眠的信号量执行一次up操作后,该信号量的值仍旧是0,但在其上睡眠的进程却少了一个。信号量的值增加1和唤醒一个进程同样也是不可分割的,不会有某个进程因执行up而阻塞,正如前面的模型中不会有进程因执行wakeup而阻塞一样。
在Dijkstra原来的论文中,他分别使用名称P和V而不是down和up,荷兰语中,Proberen的意思是尝试,Verhogen的含义是增加或升高。
从物理上说明信号量的P、V操作的含义。 P(S)表示申请一个资源,S.value>0表示有资源可用,其值为资源的数目;S.value=0表示无资源可用;S.value<0, 则|S.value|表示S等待队列中的进程个数。V(S)表示释放一个资源,信号量的初值应该大于等于0。P操作相当于“等待一个信号”,而V操作相当于“发送一个信号”,在实现同步过程中,V操作相当于发送一个信号说合作者已经完成了某项任务,在实现互斥过程中,V操作相当于发送一个信号说临界资源可用了。实际上,在实现互斥时,P、V操作相当于申请资源和释放资源。
该解决方案使用了三个信号量:一个称为full,用来记录充满缓冲槽数目,一个称为empty,记录空的缓冲槽总数;一个称为mutex,用来确保生产者和消费者不会同时访问缓冲区。full的初值为0,empty的初值为缓冲区中槽的数目,mutex的初值为1。供两个或多个进程使用的信号量,其初值为1,保证同时只有一个进程可以进入临界区,称作二元信号量。如果每个进程在进入临界区前都执行down操作,并在刚刚退出时执行一个up操作,就能够实现互斥。
在下面的例子中,我们实际上是通过两种不同的方式来使用信号量,两者之间的区别是很重要的,信号量mutex用于互斥,它用于保证任一时刻只有一个进程读写缓冲区和相关的变量。互斥是避免混乱所必需的操作。
例题:
items 就是full,记录满槽数目,初始值位0
slots 就是empty,就是缓冲区中槽的数目,初始值为10
mutex inital value = 0
那么insert function
1 |
|
remove function
1 | P(items); //满槽数目-1 |
本例中,信号量保证缓冲区满的时候生产者停止运行,缓冲区空的时候消费者停止运行。
信号量用于同步
同步
一个进程执行某个请求,如果需要一段时间才能返回信息,那么进程会一直等待下去。也就是说直到收到返回信息才继续下去。
异步
进程不需要一直等待下去,继续执行下面的操作,不管其他进程的状态。
对于无界缓冲区问题,消费者只需关心缓冲区是否空即可
。