【操作系统】信号量学习笔记(三):生产者-消费者问题及其变式


本系列博客是操作系统课堂学习和阅读Allen B. Downey的 The Little Book of Semaphores一书的学习笔记。
本篇分享信号量题目中最经典的生产者-消费者问题,侧重个人的理解,如有错漏欢迎指出。本文伪代码对于信号量的操作形式是 P/ V,并且不严格区分进程和线程。

生产者-消费者问题(producer-consumer problem)

应用:事件驱动程序(event-driven programs)。如需要响应的鼠标事件、磁盘中到达的数据流、网络上传输的包等等。

无限缓冲区生产者-消费者问题(infinite buffer producer-consumer problem)

  • 描述:有生产者、消费者两种线程,生产者不定时生产产品(produce())放入(put())缓冲区,消费者负责从缓冲区取走(get())产品并加工(process())。假定缓冲区可以放任意数量的产品。要求:
    • 缓冲区同时只能由一个线程访问;
    • 缓冲区没有产品时,消费者线程阻塞。
  • 分析:
    • 互斥关系:缓冲区一次只能一个生产者或消费者访问;
    • 同步关系:若缓冲区空,则消费者等待生产者;
    • 变量设置:为了保证对缓冲区的互斥访问,需要设置一个mutex信号量;生产者到消费者需要使用一个信号量products进行同步。
  • 伪代码:
    Semaphore mutex = 1
    Semaphore products = 0
    
    //生产者解法1(在mutex中释放信号):
    while(1) {
      produce() //生产产品
      P(mutex) 
          put() //放入产品
          V(products) //释放信号
      V(mutex)  
    }
    //生产者解法2(在mutex外释放信号):
      while(1) {
      produce() //生产产品
      P(mutex) 
          put() //放入产品
      V(mutex)  
      V(products) //释放信号
    }
    
    //消费者:
    while(1) {
      P(products) //等待信号
      P(mutex)
          get() //取出产品
      V(mutex)
      process() //加工产品
    }
    
  • 理解与讨论:
    • 本题的生产者线程有两种正确写法,其中第二种的执行效率更高。因为它在已经释放了mutex之后才释放信号量,使得被唤醒的消费者线程能够直接获得mutex;而第一种解法的消费者可能被唤醒之后不能立刻获得mutex又被挂起,造成不必要的线程切换开销。一个原则是:在没有危险的情况下,尽量将语句放在临界区外执行

    • 一个经典的会造成死锁的错误解法是:

      //消费者:
      P(mutex) //先进行P(mutex)操作
          P(products) 
          get()
      V(mutex)
      process()
      

      对于这种典型的“保持并等待”造成的死锁,Downey的评价是:

      " …any time you wait for a semaphore while holding a mutex, there is a danger of deadlock. When you arechecking a solution to a synchronization problem, you should check for this kind of deadlock."

      在类似的连续对两个不同信号量进行P操作时,原则是:先对用于同步的信号量进行P操作,再对用于互斥的mutex信号量进行P操作。

有限缓冲区生产者-消费者问题(finite buffer producer-consumer problem)

  • 描述:在上述一般的生产者、消费者问题的基础上,限制缓冲区最多只能放置N个产品。要求:如果缓冲区满,则生产者线程阻塞。
  • 分析:
    • 互斥关系:不变;
    • 同步关系:新增:若缓冲区满,则生产者需要等待消费者 ;
    • 变量设置:额外设置一个spaces信号量用于消费者到生产者的同步。
  • 伪代码(采用在mutex外释放信号的方式):
    Semaphore mutex = 1
    Semaphore product = 0
    Semaphore spaces = N
    
    //生产者线程:
    while(1) {
      produce()
      P(spaces)
      P(mutex)
          put()
      V(mutex)
      V(products)
    }
    //消费者线程:
    while(1) {
      P(product)
      P(mutex)
          get()
      V(mutex)
      V(spaces)
      process()
    }
    
  • 理解与讨论:
    • 可以看出,本题具有高度的对称性:消费者要等待生产者生产出产品,而生产者也可能需要等待消费者“生产”出空间。可以理解为一个双向的无限缓冲区生产者-消费者问题
    • 初始化时product + spaces == N这个等式一定成立,但实际执行时不一定。

缓冲区大小为1的生产者-消费者问题

  • 要求:上述有限缓冲区问题的特例:缓冲区的大小为1。
  • 分析:同步互斥情况与上一题相同,但mutex可省略,如下:
  • 伪代码:
    Semaphore product = 0
    Semaphore space = 1
    //生产者线程:
    while(1) {
        produce()
        P(space)
            put()
        V(product)
    }
    //消费者线程:
    while(1) {
        P(product)
            get()
        V(space)
        process()
    }
    
  • 理解与讨论:
    • mutex可以省略的原因是:缓冲区只能放1个产品的情况下,如果我们保证了生产者和消费者满足双向的同步关系,其实就已经顺带满足了缓冲区互斥条件。可以说,productspace这两个信号量各自承担了一部分原先mutex的职责;
    • 生产者会在space上排队,消费者会在product上排队,因此将其分别命名为producerQueueconsumerQueue也很贴切。一个生产者通过producerQueue之后,会把producerQueue锁上,并把consumerQueue解锁,让一个消费者通过;一个消费者通过consumerQueue之后,会把consumerQueue锁上,并把producerQueue解锁——这就像一个“单刀双掷开关”,任何时候只允许一方通过,并且通过之后立刻改变掷向。

参考资料:
The Little Book of Semaphores (Version 2.2.1), Allen B. Downey, 2016
缓冲区大小为1的生产者-消费者问题:https://www.cnblogs.com/lca1826/p/6589296.html

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值