在我们介绍今天的内容之前我们先了解一些相关的概念
Linux线程基本概念
Linux线程控制
Linux线程互斥
Linux线程同步
接下来我们进入今天的主题生产者消费者模型
生产者消费者模型
生产者消费者模型概念
- 321原则:3种关系,2类角色,一个交易场所
- 3种关系是:生产者与生产者之间的关系,消费者与消费者之间的关系,生产者与消费者之间的关系
- 2类角色是:一类为生产者,一类为消费者
- 一个交易场所:其实简单来说就是一块物理内存,今天我们要讲的交易场所是阻塞队列
为什么要使用生产者消费者模型
- 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。
- 生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
- 简单来说阻塞队列就是用来给生产者和消费者解耦的。
生产消费者模型的优点
- 解耦,就是让生产者和消费者之间的关联性降低
- 支持并发
- 支持忙闲不匀
基于阻塞队列的生产者消费者模型
- BlockingQueue 在多线程编程中 阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。
- 其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;
- 当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)
生产者消费者模型实现代码
- cp.hpp //头文件和相关实现文件
#ifndef __CP__HPP__
#define __CP__HPP__
#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
using namespace std;
class BlockQueue{
private:
queue<int> q;
int cap;//队列的容量
pthread_mutex_t lock; //互斥锁
pthread_cond_t full; //条件变量,判断队列是否为满
pthread_cond_t empty;
void LockQueue()
{
pthread_mutex_lock(&lock); //给队列加锁
}
void UnlockQueue()
{
pthread_mutex_unlock(&lock);//给队列解锁
}
bool QueueIsFull()
{
return q.size() == cap;
}
bool QueueIsEmpty()
{
return q.size() == 0;
}
void SignalConsumer()
{
pthread_cond_signal(&empty);//唤醒在empty的条件变量下等待的消费者
}
void ConsumerWait()
{
pthread_cond_wait(&empty,&lock);//队列为空,消费者开始等待并释放锁
}
void SignalProduct()
{
pthread_cond_signal(&full);//唤醒在full条件变量下等待的生产者
}
void ProductWait()
{
pthread_cond_wait(&full,&lock);//队列为full,生产者开始等待并释放锁
}
public:
BlockQueue(int cap_ = 32):cap(cap_)//队列初始化
{
pthread_mutex_init(&lock,NULL);
pthread_cond_init(&full,NULL);
pthread_cond_init(&empty,NULL);
}
//生产者生产数据
void PushData(const int& in)
{
LockQueue();
while(QueueIsFull()){
SignalConsumer();//若队列满了则通知消费者消费
ProductWait();//生产者停止生产
}
//队列不为满则生产者可以生产product
q.push(in);//将数据写进队列
SignalConsumer();//一旦有数据就唤醒消费者消费
UnlockQueue();
}
//消费者消费数据
void PopData(int& out)
{
LockQueue();
while(QueueIsEmpty()){
SignalProduct();//通知生产者生产
ConsumerWait();//消费者停止消费
}
//队列不为空,消费者可以消费
//consumer
out = q.front();//将队列头部数据保存下来
q.pop();//删除该数据
SignalProduct();//一旦有空间就通知生产者生产
UnlockQueue();
}
//析构函数,清理资源
~BlockQueue()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&full);
pthread_cond_destroy(&empty);
}
};
#endif
- cp.cc //主函数所在文件
#include <time.h>
#include "cp.hpp"
void *consumer(void *arg)//arg中存放的是我们创建的阻塞队列的首地址
{
int Data;
BlockQueue* bq = (BlockQueue*)arg;
for(;;){
bq->PopData(Data);
cout <<"Consumer Data: "<< Data << endl;
}
}
void *product(void *arg)
{
BlockQueue* bq = (BlockQueue*)arg;
for(;;){
int data = rand() % 100 + 1;
bq->PushData(data);
cout << "producter data: " << data << endl;
sleep(1);
}
}
int main()
{
srand((unsigned long)time(NULL));//生成随机数
BlockQueue bq(6);//阻塞队列
pthread_t c,p;//创建两个线程c为消费者,p为生产者
pthread_create(&c,NULL,consumer,(void*)&bq);//让生产者和消费者都看到该阻塞队列
pthread_create(&p,NULL,product,(void*)&bq);
pthread_join(c,NULL);
pthread_join(p,NULL);
return 0;
}
- Makefile //自动化构建工具
cp:cp.cc
g++ -o $@ $^ -lpthread
.PHONY:clean
clean:
rm -f cp