什么是生产者消费者模型?
生产者消费者模型就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里去,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者之间的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。它与普通队列的区别在于,当队列为空时,从队列取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列中放元素的操作也会被阻塞,直到有元素被从队列中取出(以上操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)。
生产者消费者模型优点:
- 解耦合。
- 支持忙闲不均。
- 支持并发。
简记:
一个场所:阻塞队列。
两种角色:生产者和消费者。
三种关系:生产者与生产者之间应具有互斥关系、消费者与消费者之间应具有互斥关系、生产者与消费者之间应具有同步与互斥关系。
生产者与消费者模型的模拟实现
使用互斥量和条件变量模拟实现:
#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>
using std::cout;
using std::endl;
// 线程数量
const int n = 4;
// 共享变量
int resources = 1;
// 阻塞队列
class BlockQueue{
public:
// 构造函数
BlockQueue(int cap = 10)
: _capacity(cap)
{
// 初始化互斥量
pthread_mutex_init(&_mutex, NULL);
// 初始化条件变量
pthread_cond_init(&_cond_pro, NULL);
pthread_cond_init(&_cond_con, NULL);
}
// 析构函数
~BlockQueue(){
// 销毁互斥量
pthread_mutex_destroy(&_mutex);
// 销毁条件变量
pthread_cond_destroy(&_cond_pro);
pthread_cond_destroy(&_cond_con);
}
public:
// 入队
bool BlockQueuePush(int data){
// 上锁
BlockQueueLock();
// 队列满
while(BlockQueueFull()){
// 等待
ProductorWait();
}
// 队列不为满,入队
_queue.push(data);
// 唤醒消费者
ConsumerWakeUp();
// 解锁
BlockQueueUnlock();
return true;
}
// 出队
bool BlockQueuePop(int* data){
// 上锁
BlockQueueLock();
// 队列为空
while(BlockQueueEmpty()){
// 消费者等待
ConsumerWait();
}
// 队列不为空,拿取队中数据
*data = _queue.front();
_queue.pop();
// 唤醒生产者
ProductorWakeUp();
// 解锁
BlockQueueUnlock();
return true;
}
private:
// 阻塞队列上锁
void BlockQueueLock(){
pthread_mutex_lock(&_mutex);
}
// 阻塞队列解锁
void BlockQueueUnlock(){
pthread_mutex_unlock(&_mutex);
}
// 阻塞队列是否为空
bool BlockQueueEmpty(){
return _queue.empty();
}
// 阻塞队列是否满
bool BlockQueueFull(){
return _queue.size() == _capacity;
}
// 生产者等待
void ProductorWait(){
pthread_cond_wait(&_cond_pro, &_mutex);
}
// 消费者等待
void ConsumerWait(){
pthread_cond_wait(&_cond_con, &_mutex);
}
// 唤醒生产者
void ProductorWakeUp(){
pthread_cond_signal(&_cond_pro);
}
// 唤醒消费者
void ConsumerWakeUp(){
pthread_cond_signal(&_cond_con);
}
private:
// 队列
std::queue<int> _queue;
// 容量
size_t _capacity;
// 互斥锁
pthread_mutex_t _mutex;
// 条件变量:生产者
pthread_cond_t _cond_pro;
// 条件变量:消费者
pthread_cond_t _cond_con;
};
// 生产者
void* thr_productor(void* arg){
BlockQueue* bq = (BlockQueue*)arg;
while(1){
cout << "productor put resources: " << resources << endl;
// 生产者生产数据放入阻塞队列
bq->BlockQueuePush(resources++);
sleep(1);
}
pthread_exit(0);
}
void* thr_consumer(void* arg){
BlockQueue* bq = (BlockQueue*)arg;
while(1){
int data;
// 消费者消费阻塞队列中的数据
bq->BlockQueuePop(&data);
cout << "consumer get data: " << data << endl;
sleep(1);
}
}
int main(){
pthread_t ptid[n], ctid[n];
BlockQueue bq;
int i, ret;
// 线程创建
for(i = 0; i < n; ++i){
ret = pthread_create(&ptid[i], NULL, thr_productor, (void*)&bq);
if(ret != 0){
// 线程创建失败
cout << "productor thread " << i << " create failed!\n";
}
ret = pthread_create(&ctid[i], NULL, thr_consumer, (void*)&bq);
if(ret != 0){
// 线程创建失败
cout << "consumer thread " << i << " create failed!\n";
}
}
// 线程等待
for(i = 0; i < n; ++i){
pthread_join(ptid[i], NULL);
pthread_join(ctid[i], NULL);
}
pthread_exit(0);
}
编译运行程序,结果如下:
使用POSIX信号量模拟实现:
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。但POSIX信号量可以用于线程间同步。
简单理解一下什么是信号量:
- 信号量是一个非负整数,所有通过它的线程/进程都会将该整数减一(通过它当然是为了使用资源),当该整数值为零时,所有试图通过它的线程都将处于等待状态。
- 在信号量上我们定义两种操作: Wait(等待) 和 Release(释放)。
- 当一个线程调用Wait操作时,它要么得到资源然后将信号量减一,要么一直等下去(指放入阻塞队列),直到信号量大于等于一时。
- Release(释放)实际上是在信号量上执行加操作,该操作之所以叫做“释放”是因为释放了由信号量守护的资源。
接口介绍:
定义信号变量。
头文件:semaphore.h
sem_t sem;
头文件:semaphore.h
功能:初始化信号量。
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
sem:信号变量。
pshared:选项标志,决定信号量用于进程间同步互斥还是线程间的同步互斥。
0:线程间。
!0:进程间。
value:信号量初始计数。
返回值:成功返回0,失败返回-1,errno被设置。
头文件:semaphore.h
功能:计数-1并等待,阻塞操作;计数<=0,阻塞。
int sem_wait(sem_t *sem);
参数:
sem:信号变量。
返回值:成功返回0,失败返回-1,errno被设置。
头文件:semaphore.h
功能:计数-1并等待,非阻塞操作;计数<=0,报错返回。
int sem_trywait(sem_t *sem);
参数:
sem:信号变量。
返回值:成功返回0,失败返回-1,errno被设置。
头文件:semaphore.h
功能:计数-1并等待,限时等待,计数<=0,计时报错返回。
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
参数:
sem:信号变量。
abs_timeout:等待时间。
返回值:成功返回0,失败返回-1,errno被设置。
头文件:semaphore.h
功能:计数+1并唤醒等待。
int sem_post(sem_t *sem);
参数:
sem:信号变量。
返回值:成功返回0,失败返回-1,errno被设置。
头文件:semaphore.h
功能:销毁信号量。
int sem_destroy(sem_t *sem);
参数:
sem:信号变量。
返回值:成功返回0,失败返回-1,errno被设置。
代码演示:
#include <iostream>
#include <vector>
#include <semaphore.h>
#include <unistd.h>
using std::cout;
using std::endl;
// 线程数量
const int n = 4;
// 资源
int resources = 1;
class CircleBlockQueue{
public:
// 构造函数
CircleBlockQueue(int cap = 10)
: _circle_queue(cap)
, _capacity(cap)
, _pro_idx(0)
, _con_idx(0)
{
// 信号量初始化
sem_init(&_sem_data, 0, 0);
sem_init(&_sem_idle, 0, cap);
sem_init(&_sem_lock, 0, 1);
}
// 析构函数
~CircleBlockQueue(){
// 销毁信号量
sem_destroy(&_sem_data);
sem_destroy(&_sem_idle);
sem_destroy(&_sem_lock);
}
// 入队
bool CircleBlockQueuePush(int data){
// 生产者等待
ProductorWait();
// 上锁
CircleBlockQueueLock();
// 数据入队
_circle_queue[_pro_idx] = data;
_pro_idx = (_pro_idx + 1) % _capacity;
// 解锁
CircleBlockQueueUnlock();
// 消费者唤醒
ConsumerWakeUp();
return true;
}
// 出队
bool CircleBlockQueuePop(int* data){
// 消费者等待
ConsumerWait();
// 上锁
CircleBlockQueueLock();
*data = _circle_queue[_con_idx];
_con_idx = (_con_idx + 1) % _capacity;
CircleBlockQueueUnlock();
ProductorWakeUp();
return true;
}
private:
// 上锁
void CircleBlockQueueLock(){
// 等待信号量
sem_wait(&_sem_lock);
}
// 解锁
void CircleBlockQueueUnlock(){
// 发布信号量
sem_post(&_sem_lock);
}
// 生产者等待
void ProductorWait(){
// 等待信号量
sem_wait(&_sem_idle);
}
// 生产者唤醒
void ProductorWakeUp(){
// 发布信号量
sem_post(&_sem_idle);
}
// 消费者等待
void ConsumerWait(){
// 等待信号量
sem_wait(&_sem_data);
}
// 消费者唤醒
void ConsumerWakeUp(){
// 发布信号量
sem_post(&_sem_data);
}
private:
// 队列
std::vector<int> _circle_queue;
// 最大容量
int _capacity;
// 生产者下标
int _pro_idx;
// 消费者下标
int _con_idx;
// 当前数量
sem_t _sem_data;
// 闲置数量
sem_t _sem_idle;
// 锁
sem_t _sem_lock;
};
// 生产者
void* thr_productor(void* arg){
CircleBlockQueue* cbq = (CircleBlockQueue*)arg;
while(1){
// 生产数据放入循环阻塞队列
cbq->CircleBlockQueuePush(resources);
cout << "productor put data: " << resources << endl;
++resources;
sleep(1);
}
pthread_exit(0);
}
// 消费者
void* thr_consumer(void* arg){
CircleBlockQueue* cbq = (CircleBlockQueue*)arg;
while(1){
int data;
// 消费者消费循环阻塞队列中的数据
cbq->CircleBlockQueuePop(&data);
cout << "consumer get data: " << data << endl;
sleep(1);
}
}
int main(){
pthread_t ptid[n], ctid[n];
CircleBlockQueue cbq;
int i, ret;
// 线程创建
for(i = 0; i < n; ++i){
ret = pthread_create(&ptid[i], NULL, thr_productor, (void*)&cbq);
if(ret != 0){
cout << "productor thread " << i << " create failed!\n";
return -1;
}
ret = pthread_create(&ctid[i], NULL, thr_consumer, (void*)&cbq);
if(ret != 0){
cout << "consumer thread " << i << " create failed!\n";
return -1;
}
}
// 线程等待
for(i = 0; i < n; ++i){
pthread_join(ptid[i], NULL);
pthread_join(ctid[i], NULL);
}
pthread_exit(0);
}
编译运行,结果如下: