Linux——生产者消费者模型
文章目录
一、生产者消费者模型引入
生产者消费者模型就是好比是一个超市,超市作为货物存放的载体,而生产者将商品上架,消费者则按需购买商品
但这个结构能运行起来需要一定的规则,作为一个超市,肯定不止一个消费者和一个生产者,同一个商品有可能是不同的生产厂商,不同的消费者可能想买相同的商品,超市内同一个位置只能放一件商品,一件商品只能被一个消费者购买,还有消费的前提是货架上有生产者放的货品等等规则,要满足上述条件,需要搞清楚生产者与消费者之间的关系
生产者与消费者之间的三种关系:
生产者之间是什么关系? 竞争—互斥
消费者和消费者之间? 竞争—互斥
生产和消费之间? 互斥&&同步
总结:
321原则:
3:3种关系
2:2种角色,生产者(1 or n),消费者(1 or n) ——线程或者进程
1:一个交易场所,内存空间
如何理解CP问题:
生产者消费者模型将生产与消费的过程进行了解耦,也就是可以边生产边消费,而不是先生产后消费,内存空间作为一个载体相当于一个仓库,暂存着数据,支持忙闲不均,可以提高数据处理的效率,因为真正在实际中耗时间的并不是拿放数据,而是生产和处理数据的过程,拿或取数据只是一瞬间的事情,数据被拿走了就不属于临界区的共享资源了,是线程自己的事情了,多线程拿走数据就能同时进行不同数据的处理,提高并发,从而提高了效率
二、基于BlockingQueue的生产者消费者模型
2.1 条件变量
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了
例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中,这种情
况就需要用到条件变量
理解条件变量:
假设线程A是苹果生产商,线程B与C恰好都需要买苹果,但此时恰好商店里的苹果断货了,需要等待线程A来补货,由于苹果是公共资源,我们知道多线程访问公共资源需要保持互斥,所以由于线程B先来到商店,所以B先申请了锁,但B发现苹果没货,又只能又释放了锁,可是B并不知道什么时候苹果有货,只能一直申请锁,访问资源发现没货,然后释放锁,如此反复进行,导致线程C并没有机会访问共享资源,C就产生了饥饿问题,因为B一直在毫无意义的申请共享资源,这就导致了资源的浪费
条件变量就像一个超市里的铃铛一样,当线程B与C在访问资源发现没货的时候就不再一直申请锁和释放锁了,而是在铃铛下排队,当有货的时候铃铛就会响,也就是唤醒B和C线程,进行消费
条件变量的作用:
- 不做无效的锁申请
- 执行具有顺序
条件变量的本质:
条件变量的接口:
初始化:
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数:
cond:要初始化的条件变量
attr:NULL
销毁:
int pthread_cond_destroy(pthread_cond_t *cond)
等待条件满足:
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量,也就是锁,这里需要传锁是因为在等待之前申请了锁,等待的时候需要释放锁,唤醒后需要重新申请锁
唤醒等待:
int pthread_cond_broadcast(pthread_cond_t *cond);
——将等待的线程全部唤醒,即广播
int pthread_cond_signal(pthread_cond_t *cond);
——唤醒一个线程
2.2 实现原理
在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构,其与普通的队列区别在于:
当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素
当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出
(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)
上文中我们介绍了生产者消费者模型,简单来说,我们需要满足321原则
321原则:
3:3种关系
2:2种角色,生产者(1 or n),消费者(1 or n) ——线程或者进程
1:一个交易场所,内存空间
- 队列我们可以使用STL中自带的queue,值得注意的是,这个队列是生产者和消费者共有的,所以生产者和消费者在维护这个队列的时候,需要保持互斥性
- 当队列为空的时候无法消费,当队列为满的时候无法生产,这是拥塞的规则,我们需要用条件变量来约束
- 当生产者Push的时候可以唤醒消费者消费,当消费者Pop的时候可以唤醒生产者生产
2.3 实现代码
BlockQueue.hpp
#pragma once
#include <queue>
#include "LockGuard.hpp"
#define MaxSize 5
template <class T>
class BlockQueue
{
public:
BlockQueue(int sz = MaxSize)
: _capacity(sz)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_pcond, nullptr);
pthread_cond_init(&_ccond, nullptr);
}
void Push(const T &data)
{
//pthread_mutex_lock(&_mutex);
LockGuard lock(&_mutex);
while (IsFull())
{
pthread_cond_wait(&_pcond, &_mutex);
}
_q.push(data);
pthread_cond_signal(&_ccond);
//pthread_mutex_unlock(&_mutex);
}
void Pop(T &data)
{
//pthread_mutex_lock(&_mutex);
LockGuard lock(&_mutex);
while (IsEmpty())
{
pthread_cond_wait(&_ccond, &_mutex);
}
data = _q.front();
_q.pop();
pthread_cond_signal(&_pcond);
//pthread_mutex_unlock(&_mutex);
}
bool IsEmpty()
{
if (_q.size() == 0)
{
return true;
}
else
{
return false;
}
}