生产者消费者模型
简述:有时候对于临界资源的访问是有限制条件的,举例说明
打印机:只有有任务来了才可以进行打印;另一方面,只有任务队列有空间了任务才能被添加。
一.条件变量
流程:封装了一个线程安全的任务队列
1.定义变量:queue(保存任务的信息)、_capacity(队列中的最大节点数目)、_mutex(对临界资源访问时要加锁)、_con(消费者)、_pro(生产者)
2.实现任务入队和任务出队:入队时,加锁、判断队列是否已满(满则等待)、添加节点、唤醒消费者、解锁
出队时:加锁、判断有无节点数据(无则等待)、拿到节点任务出队、唤醒生产者、解锁
3.记得最后销毁所有变量:锁、条件变量
//使用条件变量实现生产者/消费者模型
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <mutex>
#include <queue>
using namespace std;
const int size = 4; //表示线程的数目
const int Max = 5; //表示队列的最大容量
class BlockQueue{
private:
queue<int> _queue; //假定数据都是int类型
int _capacity;
pthread_mutex_t _mutex; //互斥锁
pthread_cond_t _pro;
pthread_cond_t _con;
public:
//1.初始化所有变量
BlockQueue(int maxq = Max):_capacity(maxq){
pthread_mutex_init(&_mutex, NULL);
pthread_cond_init(&_pro, NULL);
pthread_cond_init(&_con, NULL);
}
//2.入队
bool QueuePush(int data){
//先加锁,再判断是否已满
pthread_mutex_lock(&_mutex);
while(_queue.size() >= _capacity){
//表示已经满了,就循环等待
pthread_cond_wait(&_pro, &_mutex);
//cout << "生产产品 " << _queue.size() << "等待中!!!" << endl;
}
//有空间,就直接进行入队操作
_queue.push(data);
//cout << "产品" << data << " 生产完毕" << endl;
cout << "现在有 " << _queue.size() << "个数据" << endl;
//唤醒消费者,有数据可以处理了
pthread_cond_signal(&_con);
pthread_mutex_unlock(&_mutex);
return true;
}
//3.任务出队
bool QueuePop(int& data){
pthread_mutex_lock(&_mutex);
while(_queue.size() == 0){
pthread_cond_wait(&_con, &_mutex);
//cout << "消费产品" << _queue.size() << "等待中!!!" << endl;
}
data = _queue.front();
_queue.pop();
cout << "产品 " << _queue.size()+1 << "处理完毕" << endl;
cout << "消费完后现在有 " << _queue.size() << "个数据" << endl;
pthread_cond_signal(&_pro);
pthread_mutex_unlock(&_mutex);
return true;
}
//4.销毁所有变量
~BlockQueue(){
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_pro);
pthread_cond_destroy(&_con);
}
};
void* thr_pro(void* arg){
BlockQueue* q = (BlockQueue*)arg;
for(int i = 0;i < size;++i){
cout << endl;
cout << "生产者线程 " << i << "生产" << endl;
q->QueuePush(i);
}
return NULL;
}
void* thr_con(void* arg){
BlockQueue* q = (BlockQueue*)arg;
int data;
for(int i = 0;i < size;++i){
//cout << "消费者线程 " << i << "消费" << endl;
q->QueuePop(data);
//cout << "消费者线程 " << i << "消费数据 " << data << "完毕" << endl;
}
return NULL;
}
int main(){
pthread_t pro[size], con[size];
BlockQueue q;
for(int i = 0;i < size;++i){
pthread_create(&pro[i], NULL, thr_pro, (void*)&q);
}
for(int i = 0;i < size;++i){
pthread_create(&con[i], NULL, thr_con, (void*)&q);
}
for(int i = 0;i < size;++i){
pthread_join(pro[i], NULL);
}
for(int i = 0;i < size;++i){
pthread_join(con[i], NULL);
}
return 0;
}
二.信号量
流程:同样是封装了一个线程安全的任务队列
1.定义变量:_queue(vector类型)、_capacity、_write(读指针)、_read(写指针)、_lock(锁互斥量)、_empty_space(空闲空间)、_data_space(数据的数目)
2.int sem_init(sem_t *sem, int pshared, unsigned int value);函数进行初始化其中pshared == 0表示在线程之间使用,!=0时表示在进程之间使用,value表示该变量的初始值;
所以对于_lock来说只有0,1;开始就给1,否则无法访问到临界资源;
对于_empty_space来说,初始值就是_capacity;
对于_data_space来说,开始没有数据,所以是0.
3.sem_wait() 和 sem_post()接口表示对某个信号量-1和+1操作
如果-1后 < 0;就表示没有资源,等待;
生产者生产数据后入队,空闲空间变少了,所以要-1;之后数据就会多一个,所以数据+1。
//使用POSIX信号量实现生产者/消费者模型
#include <iostream>
#include <thread>
#include <semaphore.h>
#include <vector>
using namespace std;
const int size = 4; //最多4个线程
const int Max = 5; //最多5个节点数据
class BlockQueue{
private:
vector<int> _queue;
int _capacity;
int _write; //写指针
int _read; //读指针
sem_t _lock; //相当于锁互斥量
sem_t _empty_space; //记录空闲空间
sem_t _data_space; //记录已经存放的数据量
public:
//1.初始化
BlockQueue():_queue(Max),_capacity(Max),_write(0),_read(0){
sem_init(&_lock, 0, 1); //互斥锁的计数0/1
sem_init(&_empty_space, 0, _capacity);
sem_init(&_data_space, 0, 0);
}
//2.任务入队
bool QueuePush(int val){
sem_wait(&_empty_space);
sem_wait(&_lock);
_queue[_write] = val;
_write = (_write + 1) % _capacity;
sem_post(&_lock);
sem_post(&_data_space);
return true;
}
//3.任务出队
bool QueuePop(int& val){
sem_wait(&_data_space);
sem_wait(&_lock);
val = _queue[_read];
_read = (_read + 1) % _capacity;
sem_post(&_lock);
sem_post(&_empty_space);
return true;
}
//4.销毁
~BlockQueue(){
sem_destroy(&_lock);
sem_destroy(&_empty_space);
sem_destroy(&_data_space);
}
};
void thr_pro(BlockQueue* q){
int val = 0;
int i = 0;
for(int i = 0;i < size;++i){
q->QueuePush(val);
cout << "生产者线程 " << i << "生产 " << val << "完毕!!!" << endl;
++val;
//++i;
}
}
void thr_con(BlockQueue* q){
int val = 0;
int i = 0;
for(int i = 0;i < size;++i){
q->QueuePop(val);
cout << "消费者线程 " << i << "处理数据 " << val << "完毕!!!" << endl;
//++i;
}
}
int main(){
BlockQueue q;
vector<thread> pro(4);
vector<thread> con(4);
for(int i = 0;i < size;++i){
pro[i] = thread(&thr_pro, &q);
}
for(int i = 0;i < size;++i){
con[i] = thread(&thr_con, &q);
}
for(int i = 0;i < size;++i){
pro[i].join();
con[i].join();
}
return 0;
}
注意:
1.条件变量需要搭配锁互斥量一起使用,而信号量则不用(本身的计数即可实现锁)
2.C++中的thread库,参数只有入口函数和入口函数的参数即可,但是入口函数的结构与前者不同
void* thr(void* arg);
void thr(类型自己设置);