有界缓冲区问题

  考虑生产者-消费者问题(也称作有界缓冲区问题)。两个进程共享一个公共的固定大小的缓冲区。

其中的一个,生产者,将信息放入缓冲区;另一个,消费者,从缓冲区中取出信息(该问题也可被推

广到m个生产者,n个消费者的情况,但出于简单起见,我们只考虑一个生产者,一个消费者的情况)。


  一般考虑的调度算法:

  

  但该算法会有出现错误的情况:

  当count为0,消费者刚执行完(count == 0)的判断时,调度者启用了生产者的进程,生者向缓冲区中加

入一条数据,并将count递增为1;

  但由于生产者认为count值在被自己递增前是0,也就是说消费者此时在sleep(虽然消费者并没有sleep,

但生产者是这么认为的),因此生产者调用wakeup唤醒消费者;

  收到wakeup信号的消费者会丢弃该信号(因为自己并没有在sleep),消费者继续从原处执行,即sleep()

被执行;

  此时便出现了一个有趣的情况,调度者再一次启用生产者的进程后,由于count只增不减,因此永远也不会

再调用wakeup(consumer),消费者也将一直sleep下去,缓冲区会被填满。

  这里问题的实质在于发给一个(尚)未睡眠进程的唤醒信号丢失了。如果它没有丢失,则一切都很正常。

一种快速的弥补方法是修改规则,加上一个唤醒等待位(wakeup waitingbit)。当向一个清醒的进程发送一

个唤醒信号时,将该位置位。随后,当进程要睡眠时,如果唤醒等待位为1,则将该位清除,而进程仍然

保持清醒。

尽管在本例中唤醒等待位解决了问题,但很容易就可以构造出一些例子,其中有两个或更多的进程,这时

一个唤醒等待位就不敷使用。我们可以再打一个补丁,加入第二个唤醒等待位,或者甚至是8个、32个,

但原则上讲这并未解决问题。


  信号量的方法:

  Dijkstra建议设两种操作:DOWN和UP(分别为推广后的SLEEP和WAKEUP)。对一信号量执行DOWN

操作是检查其值是否大于0。若是则将其值减1(即,用掉一个保存的唤醒信号)并继续。若值为0,则进程

将睡眠,而且此时DOWN操作并未结束。检查数值、改变数值、以及可能发生的睡眠操作均作为一个单一的

、不可分割的原子操作(atomic action)完成。即保证一旦一个信号量操作开始,则在操作完成或阻塞之前别的

进程均不允许访问该信号量。这种原子性对于解决同步问题和避免竞争条件是非常重要的。UP操作递增信号

量的值。如果一个或多个进程在该信号量上睡眠,无法完成一个先前的DOWN操作,则由系统选择其中的一

个(例如,随机挑选)并允许其完成它的DOWN操作。于是,对一个有进程在其上睡眠的信号量执行一次UP

操作之后,该信号量的值仍旧是0,但在其上睡眠的进程却少了一个。递增信号量的值和唤醒一个进程同样也

是不可分割的。


  我们看该方法,由于down和up都是原子操作,因此就没有了上一个算法出现的问题。另外,mutex提供了

互斥机制,down(&mutex);F1();up(&mutex); 由于mutex初值被置为1,因此保证了down和up两句之间的语句,

是不允许其它进程参与的。比如,F1执行过程中,调度者启用了另一个进程,其有down(&mutex);F2();

up(&mutex);  显然,down(&mutex) 将导致该进程被阻塞,因为mutex值为0。当F1执行完,执行up(&mutex)

后,由于mutex这个信号量上有进程阻塞在down(&mutex)上(将要执行F2()的进程),该被阻塞的进程将被

唤醒。mutex的值只在0和1中,扮演了锁的角色。

  另外,需要注意,假设将生产者代码中的两个DOWN操作交换一下次序,将使得mutex的值在empty之前被

减1,而不是在其之后。如果缓冲区是满的,即empty为0,生产者将阻塞,mutex值为0。这样一来,当消费

者下次试图访问缓冲区时,它将对mutex的执行一个DOWN操作,由于mutex值为0,则它也将阻塞。这将造

成死锁。


  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值