生产者消费者模型
生产者消费者模型概念
生产者消费者模型又称有限缓冲问题,是一个多进程同步问题的经典案例。该问题描述了共享固定大小缓冲区的两个进程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。
如下图:
生产者消费者模型特点
生产者消费者是多线程同步于互斥的经典场景,其特点如下:
3种关系:
生产者VS生产者(互斥关系)、生产者VS消费者(同步关系)、消费者VS消费者(互斥关系)2种角色:
生产者和消费者1个交易场所:
通常指内存中的一段缓冲区
我们用代码实现的话就要维护上面的3种关系即可。
生产者和生产者、消费者和消费者为什么要互斥?
当1个生产者开始往缓冲区里写入数据时当然不希望有其他的生产者进入缓冲区,所以要在缓冲区用互斥锁保护起来。同样,1个消费者读取数据也要避免其他的消费者干扰,所以在1个消费者读取缓冲区数据时用互斥锁保护起来。
生产者和消费者同步关系?
当生产者把缓冲区数据加满后,再生产数据就会失败。消费者把缓冲区数据消费完再消费也会失败。
所以让生产者在缓冲区满时休眠,等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。
生产者消费者模型优点
支持并发
由于生产者与消费者是两个独立的并发体,他们之间是用缓冲区作为桥梁连接,生产者只需要往缓冲区里丢数据,就可以继续生产下一个数据,而消费者只需要从缓冲区了拿数据即可,这样就不会因为彼此的处理速度而发生阻塞。
解耦
支持忙时不均
如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。 等生产者的制造速度慢下来,消费者再慢慢处理掉。
基于BlockingQueue的生产者消费者模型
基于阻塞队列的生产者消费者模型
在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。
和普通队列的区别:
- 当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素
- 当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)
模拟实现生产者消费者模型
我们先模拟1个单生产者和单消费者
这个BlockQueue我们就用C++STL中的Queue即可。
整体的逻辑:
- 现在实现单生产者和单消费者,那我们维护生产者和消费者的同步关系即可
- 设置BlockQueue的存储数据上限,不能让生产者一直生产下去,具体的实现根据业务的需求
- BlockQueue是临界资源,所以需要1把互斥锁进行保护
- 当生产者向阻塞队列中放入数据时,若此时阻塞队列满了,那生产者此时要
先通知消费者
开始消费数据,自己挂起 - 当消费者取阻塞队列中的数据时,若此时阻塞队列为空了,那消费者
先通知生产者
开始生产数据,自己挂起 - 因此我们还需要2个条件变量,1个当阻塞队列满时,生产者需要在此条件变量下等待。1个条件变量当阻塞队列为空时,消费者在此条件变量下等待
- 无论是生产者还是消费者都要进入临界区判断条件是否满足,若条件不满足就要被挂起,此时它们中的1个是拿着锁在等待的,为了避免死锁的问题,在调用
pthread_cond_wait
时要传入互斥锁,当被挂起时释放手中的锁资源 - 主函数创建2个线程,1个线程不断生产数据,另一个线程不断的读取数据即可
代码如下:
BlockQueue:
1 #include<iostream>
2 #include<pthread.h>
3 #include<queue>
4 using namespace std;
5
6
7 class BlockQueue
8 {
9 private:
10 pthread_mutex_t mutex;
11 pthread_cond_t c_cond; // 消费者在次条件变量下等
12 pthread_cond_t p_cond; // 生产者在此条件变量下等
13 size_t cap;
14 queue<int> q;
15 public:
16 BlockQueue(int _cap = 5):cap(_cap)
17 {
}
18 void get_init()
19 {
20 pthread_mutex_init(&mutex,nullptr);
21 pthread_cond_init(&c_cond,nullptr);
22 pthread_cond_init(&p_cond,nullptr);
23 }
24 void LockQueue()
25 {
26 pthread_mutex_lock(&mutex);
27 }
28 void UnLockQueue()
29 {
30 pthread_mutex_unlock(&mutex);
31 }
32 void ProductWait()
33 {
34 pthread_cond_wait(&p_cond,&mutex);
35 }
36 void ConsumerWait()
36 void ConsumerWait()
37 {
38 pthread_cond_wait(&c_cond,&mutex);
39 }
40 void WakeUpConsumer()
41 {
42 pthread_cond_signal(&c_cond);
43 }
44 void WakeUpProduct()
45 {
46 pthread_cond_signal(&p_cond);
47 }
48 bool IsFull()
49 {
50 r