生产者消费者问题是计算机中一类重要的模型,主要描述的是:生产者往缓冲区中放入产品、消费者取走产品。生产者和消费者指的可以是线程也可以是进程。

生产者消费者问题的难点在于:

为了缓冲区数据的安全性,一次只允许一个线程进入缓冲区,它就是所谓的临界资源。

生产者往缓冲区放物品时,如果缓冲区已满,那么需要等待,一直到消费者取走产品为止。

消费者取走产品时,如果没有物品,需要等待,一直到有生产者放入为止。

第一个问题属于互斥问题,我们需要使用一把互斥锁,来实现对缓冲区的安全访问。

后两个属于同步问题,两类线程相互协作,需要两个条件变量,一个用于通知生产者放入产品,一个用来通知消费者取走物品。

生产者线程的大概流程是:

1.加锁

2.如果缓冲区已满,则等待。

3.放入物品

4.解锁

5.通知消费者,可以取走产品

消费者的逻辑恰好相反:

1.加锁

2.缓冲区为空,等待

3.取走物品

4.解锁

5.通知生产者,可以放入物品

我们设计出一个缓冲区:

  1. #ifndef BUFFER_H_
  2. #define BUFFER_H_
  3.  
  4. #include "NonCopyable.h"
  5. #include "MutexLock.h"
  6. #include "Condition.h"
  7. #include <queue>
  8.  
  9. class Buffer : NonCopyable
  10. {
  11. public:
  12. Buffer(size_t size);
  13. void push(int val);
  14. int pop();
  15.  
  16. bool empty() const;
  17. size_t size() const;
  18.  
  19. private:
  20. mutable MutexLock mutex_;
  21. Condition full_;
  22. Condition empty_;
  23.  
  24. size_t size_; //缓冲区的大小
  25. std::queue<int> queue_;
  26. };
  27.  
  28. #endif //BUFFER_H_

这里注意,我们把同步与互斥的操作都放入Buffer中,使得生产者和消费者线程不必考虑其中细节,这符号软件设计的“高内聚、低耦合”原则。

还有一点,mutex被声明为mutable类型,意味着mutex在const函数中仍然可以被改变,这是符合程序的逻辑的。把mutex声明为mutable,是一种标准实践。

重点是push和pop的实现:

  1. void Buffer::push(int val)
  2. {
  3. //lock
  4. //wait
  5. //push
  6. //notify
  7. //lock
  8. {
  9. MutexLockGuard lock(mutex_);
  10. while(queue_.size() >= size_)
  11. empty_.wait();
  12. queue_.push(val);
  13. }
  14. full_.notify();
  15. }
  16.  
  17. int Buffer::pop()
  18. {
  19. int temp = 0;
  20. {
  21. MutexLockGuard lock(mutex_);
  22. while(queue_.empty())
  23. full_.wait();
  24. temp = queue_.front();
  25. queue_.pop();
  26. }
  27. empty_.notify();
  28.  
  29. return temp;
  30. }

这里注意:

1.条件变量的等待必须使用while,这是一种最佳实践,原因可见Condition的封装Linux组件封装(二)中条件变量Condition的封装

2.可以先notify再解锁,也可以先解锁。不过推荐先解锁,原因是如果先notify,唤醒一个线程B,但是还未解锁,此时如果线程切换至刚唤醒的线程B,B马上尝试lock,但是肯定失败,然后阻塞,这增加了一次线程切换的开销

我们还可以继续封装,将缓冲区与多个生产者、消费者封装成一个车间类,如下:

  1. #ifndef WORKSHOP_H_
  2. #define WORKSHOP_H_
  3.  
  4. #include "NonCopyable.h"
  5. #include "Buffer.h"
  6. #include <vector>
  7.  
  8. class ProducerThread;
  9. class ConsumerThread;
  10.  
  11. class WorkShop : NonCopyable
  12. {
  13. public:
  14. WorkShop(size_t bufferSize,
  15. size_t producerSize,
  16. size_t consumerSize);
  17. ~WorkShop();
  18.  
  19. void startWorking();
  20. private:
  21. size_t bufferSize_;
  22. Buffer buffer_;
  23.  
  24. size_t producerSize_;
  25. size_t consumerSize_;
  26. std::vector<ProducerThread*> producers_;
  27. std::vector<ConsumerThread*> consumers_;
  28. };
  29.  
  30. #endif //WORKSHOP_H_

这样就可以方便的指定线程的数目。

 

完整的项目代码请参见这里:生产者消费者完整代码

Linux组件封装(五)一个生产者消费者问题示例的更多相关文章

  1. 并发编程(二):分析Boost对 互斥量和条件变量的封装及实现生产者消费者问题

    请阅读上篇文章<并发编程实战: POSIX 使用互斥量和条件变量实现生产者/消费者问题>.当然不阅读亦不影响本篇文章的阅读. Boost的互斥量,条件变量做了很好的封装,因此比" ...

  2. 并发编程入门(二):分析Boost对 互斥量和条件变量的封装及实现生产者消费者问题

    请阅读上篇文章<并发编程实战: POSIX 使用互斥量和条件变量实现生产者/消费者问题>.当然不阅读亦不影响本篇文章的阅读. Boost的互斥量,条件变量做了很好的封装,因此比" ...

  3. Linux多线程之同步2 —— 生产者消费者模型

    思路 生产者和消费者(互斥与同步).资源用队列模拟(要上锁,一个时间只能有一个线程操作队列). m个生产者.拿到锁,且产品不满,才能生产.当产品满,则等待,等待消费者唤醒.当产品由空到不空,通知消费者 ...

  4. 五、生产者消费者模型_ThreadLocal

    1.生产者消费者模型作用和示例如下:1)通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率 ,这是生产者消费者模型最重要的作用2)解耦,这是生产者消费者模型附带的作用,解耦意味着生产者 ...

  5. 用阻塞队列实现一个生产者消费者模型?synchronized和lock有什么区别?

    多线程当中的阻塞队列 主要实现类有 ArrayBlockingQueue是一个基于数组结构的有界阻塞队列,此队列按FIFO原则对元素进行排序 LinkedBlockingQueue是一个基于链表结构的 ...

  6. 用Java写一个生产者-消费者队列

    生产者消费者的模型作用 通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这是生产者消费者模型最重要的作用. 解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系 ...

  7. golang的生产者消费者模型示例

    package main import "fmt" func Producer(ch chan int) { for i := 1; i <= 10; i++ { ch &l ...

  8. Python 再次改进版通过队列实现一个生产者消费者模型

    import time from multiprocessing import Process,Queue #生产者 def producer(q): for i in range(10): time ...

  9. Linux组件封装(一)中互斥锁MutexLock的封装

    本文对Linux中的pthread_mutex_t做一个简易的封装. 互斥锁主要用于互斥,互斥是一种竞争关系,主要是某一个系统资源或一段代码,一次做多被一个线程访问. 条件变量主要用于同步,用于协调线 ...

随机推荐

  1. [ CodeVS冲杯之路 ] P1010

    不充钱,你怎么AC? 题目:http://codevs.cn/problem/1010/ 首先我们将坐标都+1,因为它是从(0,0)开始的 预处理出禁区,也就是马能到达的格子和马自己的格子,标上记号 ...

  2. Video for Linux Two API Specification Revision 2.6.32【转】

    转自:https://www.linuxtv.org/downloads/legacy/video4linux/API/V4L2_API/spec-single/v4l2.html Video for ...

  3. malloc()与calloc区别【转】

    转自:http://blog.csdn.net/firecityplans/article/details/4490124/ 版权声明:本文为博主原创文章,未经博主允许不得转载. Both the m ...

  4. Linux下使用system()函数一定要谨慎

    转载自:http://my.oschina.net/renhc/blog/53580   linux尽量避免使用system. 曾经的曾经,被system()函数折磨过,之所以这样,是因为对syste ...

  5. 使用windos电脑模拟搭建web集群(一)

    资源规划 1.环境准备 centos7.2 虚拟机 13个 可以先配置一台,做好基础优化,然后克隆13台分布在 windos宿主机上 两台windos笔记  都是8g的内存 一台有点内存吃紧. 没有物 ...

  6. PSR-1 基础编码规范

    本篇规范制定了代码基本元素的相关标准, 以确保共享的PHP代码间具有较高程度的技术互通性. 关键词 “必须”("MUST").“一定不可/一定不能”("MUST NOT& ...

  7. UpdateLayeredWindow后,使用Gdi DrawText文字透明的解决办法

    来源:http://stackoverflow.com/questions/5309914/updatelayeredwindow-and-drawtext 要点就是在先在memDc DrawText ...

  8. Strobogrammatic Number II -- LeetCode

    A strobogrammatic number is a number that looks the same when rotated 180 degrees (looked at upside ...

  9. 设置iframe高度自适应屏幕高度

    写在前面: 最近在搭建项目前台页面框子的时候,把iframe设置成了固定的高度,导致不同的电脑尺寸访问的时候,高度差异较大,故查了下,将iframe设置成自动适应屏幕高度的方式,这里记录下. 还是直接 ...

  10. 九. 常用类库、向量与哈希1.Java基础类库

    Java 的类库是 Java 语言提供的已经实现的标准类的集合,是 Java 编程的 API(Application Program Interface),它可以帮助开发者方便.快捷地开发 Java ...