生产者消费者模型
应用场景
有线程不断的生产数据,有线程不断的处理数据
数据的生产与数据的处理,放在同一个线程中完成,因为执行流只有一个,那么肯定是生产一个处理一个,处理完一个后才能生产一个
- 这样的话依赖关系太强 - 如果处理比较慢,也会拖的生产速度慢下来
- 因此为了提高效率,将生产与处理放到不同的执行流中完成,中间增加一个数据缓冲区,作为中间的数据缓冲场所
概念
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯, 而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
优点:
- 解耦合 (生产是一个功能/处理也是一个功能。如果放到一起,相互受到的影响就比较大; 解耦合,将各个功能分离开来,降低相互影响的程度)
- 支持并发
- 支持忙闲不均
并发:轮询处理(任务一个一个处理)
并行:同时处理(cpu资源多的情况可以支持)
而这里的支持并发:指的是可以有多个执行流处理
基于BlockingQueue的生产者消费者模型
在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于:
- 当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素
- 当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出
以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞
实现:
- 一个场所(线程安全的缓冲区)- (数据队列)
- 两种角色(生产者与消费者)
- 三种关系(实现线程安全)
生产者与消费者,其实只是两种业务处理的线程而已:
创建线程就可以
实现的关键在于线程安全的队列:
封装一个线程安全的BlockQueue - 阻塞队列 - 向外提供线程安全的入队/出队操作
模版:
Class BlockQueue{
public:
BlockQueue();
// 编码风格: 纯输入参数 const int & / 输出型参数 指针 / 输入输出型 &
bool Push(int data); // 入队数据
bool Pop(int *data); // 出队数据
private:
std::queue<int> _queue; // STL中queue容器,是非线程安全的 - 因为STL设计之初就是奔着性能去的(功能多了,耦合度就高了)
int _capacity; // 队列中节点的最大数量(数据也不能无限制添加,内存耗尽程序就崩溃了)
pthread_mutex_t _mutex;
pthread_cond_t _productor_cond; // 生产者队列
pthread_cond_t _customer_cond; // 消费者队列
}
代码示例:
#include <iostream>
#include <cstdio>
#include <queue>
#include <pthread.h>
using namespace std;
#define QUEUE_MAX 5
// 线程安全的阻塞队列 - 没有数据则消费者阻塞 / 数据满了则生产者阻塞
class BlockQueue{
public:
BlockQueue(int maxq = QUEUE_MAX):_capacity(maxq){
pthread_mutex_init(&_mutex, NULL);
pthread_cond_init(&_pro_cond, NULL);
pthread_cond_init(&_cus_cond, NULL);
}
~BlockQueue(){
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_pro_cond);
pthread_cond_destroy(&_cus_cond);
}
bool Push(int data){
// 生产者才会入队数据,如果队列中数据满了则需要阻塞
pthread_mutex_lock(&_mutex);
// _queue.size() 获取队列节点个数
while(_queue.size() == _capacity){
pthread_cond_wait(&_pro_cond, &_mutex);
}
_queue.push(data); // _queue.push()入队操作
pthread_mutex_unlock(&_mutex); // 解锁
pthread_cond_signal(&_cus_cond); // 唤醒消费者
return true;
}
// 使用指针,表示这是一个输出型数据,用于返回数据
bool Pop (int *data){
// 出队都是消费者,有数据才能出队,没有数据要阻塞
pthread_mutex_lock(&_mutex);
//_queue.empty() 若queue为NULL,则返回true
while(_queue.empty()){
pthread_cond_wait(&_cus_cond, &_mutex);
}
*data = _queue.front(); // _queue.front() 获取队首节点数据
_queue.pop(); // 出队
pthread_mutex_unlock(&_mutex);
pthread_cond_signal(&_pro_cond);
return true;
}
private:
std::queue<int> _queue;
int _capacity;
pthread_mutex_t _mutex;
pthread_cond_t _pro_cond;
pthread_cond_t _cus_cond;
};
void *thr_productor(void *arg){
BlockQueue *queue = (BlockQueue*)arg;
int i = 0;
while(1){
// 生产者不断生产数据
queue->Push(i);
printf("productor push data:%d\n", i++);
}
return NULL;
}
void * thr_customer(void *arg){
BlockQueue *queue = (BlockQueue*)arg;
while(1){
// 消费者不断获取数据进行处理
int data;
queue->Pop(&data);
printf("customer pop data:%d\n", data);
}
return NULL;
}
int main(){
int ret, i;
pthread_t ptid[4], ctid[4];
BlockQueue queue;
for(i = 0; i < 4; i++){
ret = pthread_create(&ptid[i], NULL, thr_productor, (void*)&queue);
if(ret != 0){
printf("create productor thread error\n");
return -1;
}
ret = pthread_create(&ctid[i], NULL, thr_customer, (void*)&queue);
if(ret != 0){
printf("create customer thread error\n");
return -1;
}
}
for(i = 0; i < 4; i++){
pthread_join(ptid[i], NULL);
pthread_join(ctid[i], NULL);
}
return 0;
}
一次生成结果:
productor push data:79517
productor push data:79518
productor push data:79519
productor push data:79520
productor push data:79521
customer pop data:79517
customer pop data:79518
customer pop data:79519
customer pop data:79520
customer pop data:79521
productor push data:75501
productor push data:75502
productor push data:75503
productor push data:75504
productor push data:75505
customer pop data:75501
customer pop data:75502
customer pop data:75503
customer pop data:75504
customer pop data:75505
...
productor push data:79522
productor push data:79523
productor push data:79524
productor push data:79525
productor push data:79526
customer pop data:79522
customer pop data:79523
customer pop data:79524
customer pop data:79525
customer pop data:79526
productor push data:75506
productor push data:75507
productor push data:75508
productor push data:75509
productor push data:75510
customer pop data:75506
customer pop data:75507
customer pop data:75508
customer pop data:75509
customer pop data:75510
如果本篇博文有帮助到您的理解,留个赞激励博主呀~~