目录
一.概念介绍
二.基于阻塞队列的实现
(一).实现逻辑
(二).示例代码
生产消费者模型是操作系统里非常经典模型,可应用于多线程并发协作,本质即通过一个容器(即缓冲区,本质是一种数据结构)来解决生产者与消费者的强耦合问题,也就是实现数据交互的低耦合。
模型图示如下:
在生产消费者模型中,生产者将产生的数据放入容器里,消费者从容器中获取数据来使用。而不是由生产者直接将数据“交给”消费者。相当于生产者把数据放入了一个“仓库”,消费者直接从“仓库”里拿,从而避免了生产者和消费者的直接接触。
这样做的好处是当消费者处理速度慢于生产者时,生产者可以不用阻塞等待消费者处理而继续生产数据至容器中,提高效率。生产者速度慢于消费者时同理。
还有就是提高并发度,比如有多个生产者时,一个生产者向容器写入数据,其他生产者可以继续从上层获取数据,不耽误数据的生产。说的再直白一点,数据的生产过程不一定在生产者,生产者也是从上层获取的数据,这样做上层可以继续产出数据而不是阻塞。就实际开发而言,生产者向容器写入数据的时间远小于上层产出数据的时间。因此,提高并发度并不是可以有多个生产者同时向容器写数据,而是能够并发的产出数据,最大限度的提高时间效率。
一个非常直观的生产消费者模型就是管道。管道本身就是一个容器,写端是生产者,向管道写入数据就是生产者写入容器的过程;读端是消费者,读取数据并处理就是从容器获取资源并消费。
同时容器的特性与管道一样,当容器满了,生产者会阻塞,这对应管道写满数据,写端阻塞;同理,容器空了消费者阻塞对应管道空时读端阻塞。
生产消费者模型有很多种实现方式,本文选择阻塞队列的实现方式进行演示。
实现生产消费者模型的重点在于实现数据交互的容器。生产者和消费者本质都是线程,一个向容器中写入数据另一个从容器中取得数据。
容器中主要的函数有两个:输入数据和输出数据。
这两个函数需要用到同一个互斥锁来保证同一时间只能有一个线程访问该容器。也就是说同一时间,生产者间互斥、消费者间互斥、生产者与消费者间互斥。
并且当容器数据满时,需要让生产者阻塞;容器数据空时,需要让消费者阻塞。即这两个函数内部需要根据当前容器存储情况决定是否阻塞该线程。因为两个函数应用于两种不同的线程组(生产者线程组和消费者线程组),因此还需要再定义两个条件变量,分别用于阻塞生产者和消费者,使消费者或生产者能够获得该互斥锁进而使用或生产数据。
①容器(阻塞队列)
//
template
class blockQueue{
bool full(){
return _qu.size() == _capacity;
}
bool empty(){
return _qu.size() == 0;
}
public:
blockQueue(const size_t capacity)
:_capacity(capacity)
{
pthread_mutex_init(&_mtx, nullptr);
pthread_cond_init(&_full, nullptr);
pthread_cond_init(&_empty, nullptr);
}
//输入数据,pop函数与此类似
void push(const T& val){
pthread_mutex_lock(&_mtx);//加锁
//当容器满时,使用条件变量阻塞生产者,同时打开互斥锁使消费者获取数据
/*
while循环判断而非if条件判断是因为多线程时,可能cond_wait是被伪唤醒或
其他生产者已经生产数据导致仓库又满了,
但是本线程已经判断完毕,再生产数据将溢出
*/
while(full()) pthread_cond_wait(&_full, &_mtx);
_qu.push(val);//输入数据
//std::cout << "producter: " << pthread_self() << ": " << val << std::endl;
pthread_cond_signal(&_empty);//发送信号给消费者,尤其是消费者阻塞时
pthread_mutex_unlock(&_mtx);//解锁
}
//输出数据,val为输出型参数
void pop(T* val){
pthread_mutex_lock(&_mtx);
while(empty()) pthread_cond_wait(&_empty, &_mtx);
*val = _qu.front();
_qu.pop();
//std::cout << "consumer: " << pthread_self() << ": " << *val << std::endl;
pthread_cond_signal(&_full);
pthread_mutex_unlock(&_mtx);
}
~blockQueue(){
pthread_mutex_destroy(&_mtx);
pthread_cond_destroy(&_full);
pthread_cond_destroy(&_empty);
}
private:
std::queue _qu;
size_t _capacity;//容器最大容量
pthread_mutex_t _mtx;//整体互斥锁
pthread_cond_t _full;//条件变量,用于容器满时阻塞生产者
pthread_cond_t _empty;//条件变量,用于容器满时阻塞消费者者
};
②测试(生产者&消费者)
#include
#include
#include
#include
#include"BlockQueue.hpp"
using namespace std;
#define THREAD_NUM 2//消费/生产者数量
#define DATA_TYPE int//容器存储数据的类型
#define DATA_MAX_NUM 10//容器大小
using f = void(*)(blockQueue *bq);
//结构体记录容器和属于生产/消费者的函数,作为参数传递给线程函数
struct funcArgs{
funcArgs(blockQueue *bq, f func)
:_bq(bq), _func(func)
{}
blockQueue *_bq;
f _func;
};
void consumer(blockQueue *bq){//消费者
DATA_TYPE data;
bq->pop(&data);
//cout << "consumer " << pthread_self() << ": " << data << endl;
sleep(3);
}
void producter(blockQueue *bq){//生产者
DATA_TYPE data = rand() % 1234;
bq->push(data);
//cout << "producter: " << pthread_self() << ": " << data << endl;
//sleep(1);
}
void* control(void* arg){//线程函数
funcArgs* fa = (funcArgs*)arg;
while(true){
fa->_func(fa->_bq);
}
delete fa;
}
int main(){
srand(time(nullptr) * getpid() * 1023);
pthread_t prod_tid[THREAD_NUM];
pthread_t cons_tid[THREAD_NUM];
//创建容器
blockQueue* bq = new blockQueue((size_t)DATA_MAX_NUM);
//创建生产者
for(int i = 0; i < THREAD_NUM; i++){
funcArgs* args = new funcArgs(bq, producter);
pthread_create(prod_tid + i, nullptr, control, args);
}
//创建消费者
for(int i = 0; i < THREAD_NUM; i++){
funcArgs* args = new funcArgs(bq, consumer);
pthread_create(cons_tid + i, nullptr, control, args);
}
//线程回收
for(int i = 0; i < THREAD_NUM; i++){
pthread_join(prod_tid[i], nullptr);
}
for(int i = 0; i < THREAD_NUM; i++){
pthread_join(cons_tid[i], nullptr);
}
delete bq;
return 0;
}
如有错误,敬请斧正