生产者消费者模型,它是一个多线程场景中的典型应用,应用广泛,完成某些操作的时候,需要一些数据,这样的数据可能由专门的线程/进程产生,在由专门的线程/进程使用。
生产者消费者模型,它需要一个交易场所(存储数据的地方,可能是一个队列,也可能是一个栈,或者其他数据结构)。生产者负责生产数据,把数据放到交易场所中,消费者负责消费数据,把数据冲交易场所中取出。
生产者与生产者之间是互斥关系。
消费者与消费者之间是互斥关系。
生产者与消费者之间是同步和互斥关系。
首先我们只有互斥锁来模拟这个创景
#include<stdio.h>
#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<vector>
using namespace std; //只加上了互斥锁,保证了生产者之间,消费者之间的互斥
//实现一个生产者消费者模型
//首先要有一个交易场所
vector<int> data;//全局
pthread_mutex_t mt;
void* Product(void* arg)
{
(void)arg;
//负责把数据往交易场所取放
int count=0;
while(1)
{
pthread_mutex_lock(&mt);
data.push_back(++count);
pthread_mutex_unlock(&mt);
usleep(789789);
}
return NULL;
}
void* Consum(void* arg)
{
(void)arg;
//负责把交易场所中的数据取出来
while(1)
{
//每次取最后一个元素
pthread_mutex_lock(&mt);
if(!data.empty())
{
int result=data.back();//如果为空内存访问越界
data.pop_back();
printf("result=%d\n",result);
}
pthread_mutex_unlock(&mt);
usleep(123123);
}
return NULL;
}
int main()
{
pthread_mutex_init(&mt,NULL);
pthread_t tid1,tid2,tid3,tid4;
pthread_create(&tid1,NULL,Product,NULL);
pthread_create(&tid2,NULL,Product,NULL);
pthread_create(&tid3,NULL,Consum,NULL);
pthread_create(&tid4,NULL,Consum,NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
pthread_join(tid4,NULL);
pthread_mutex_destroy(&mt);
return 0;
}
这份代码只是保证了生产者与生产者之间的互斥关系,消费者与消费者之间的互斥关系。没有保证生产者与消费者之间的同步,可能会出现线程饿死。为了解决这个问题,我们加上条件变量。
#include<iostream> //增加同步!!!
#include<unistd.h>
#include<pthread.h>
#include<vector>
using namespace std;
//实现一个生产者消费者模型
//首先要有一个交易场所
vector<int> data;//全局
pthread_mutex_t mt;
pthread_cond_t cond;
void* Product(void* arg)
{
(void)arg;
//负责把数据往交易场所取放
int count=0;
while(1)
{
pthread_mutex_lock(&mt);
data.push_back(++count);
pthread_mutex_unlock(&mt);
pthread_cond_signal(&cond);
usleep(789789);
}
return NULL;
}
void* Consum(void* arg)
{
(void)arg;
//负责把交易场所中的数据取出来
while(1)
{
//每次取最后一个元素
pthread_mutex_lock(&mt);
//这个代码最好写出while 而不是if
//pthread_cond_wait 返回不一定就是其他线程signal
//这个函数也可能被信号打断!
while(data.empty())
{
//1.先释放锁
//2.等待条件就绪(有其他线程调用pthrad_cond_signal)
//3.如果条件就绪了,重新获取锁
//1,2必须是原子操作
//加上了wait之后最大的意义在于如果没有数据,消费者线程
//不必进行空转,节省了资源
//没数据,就会在这个等!!!
pthread_cond_wait(&cond,&mt);
}
int result=data.back();//如果为空内存访问越界
data.pop_back();
printf("result=%d\n",result);
pthread_mutex_unlock(&mt);
usleep(123123);
}
return NULL;
}
int main()
{
pthread_cond_init(&cond,NULL);
pthread_mutex_init(&mt,NULL);
pthread_t tid1,tid2,tid3,tid4;
pthread_create(&tid1,NULL,Product,NULL);
pthread_create(&tid2,NULL,Product,NULL);
pthread_create(&tid3,NULL,Consum,NULL);
pthread_create(&tid4,NULL,Consum,NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
pthread_join(tid4,NULL);
pthread_mutex_destroy(&mt);
pthread_cond_destroy(&cond);
return 0;
}
增加同步的意义是,避免了消费者频繁的空转,防止线程饥饿,减少调度的开销。
同步与互斥不一定非得用互斥锁和条件变量来实现,可以用信号量来模拟一个阻塞队列BlockingQueue来完成。
信号量本质是一个计数器,表示可用资源的个数。
//初始化信号量
int sem_init();
//销毁信号量
int sem_destroy()
//等待信号量
int sem_wait()
//发布信号量
int sem_post()
使用信号量时,sem_wait()操作用来申请资源,计数器-1,sem_post()操作来释放资源,计数器+1,当计算器为0时,再去操作就会发生阻塞。信号量这个计算操作是原子的。
//hpp 声明与实现在一起用hpp
#pragma once
///实现阻塞队列的代码!!!!!!!
#include<semaphore.h>
#include<unistd.h>
#include<cstdio>
#include<pthread.h>
#include<vector>
//阻塞队列:一种常见的数据结构,线程安全版本的队列
//一般Blockingqueue,都要求长度有上限
//如果队列为空,去执行pop会阻塞
//如果队列满了,去执行push也会阻塞
//信号量表示互斥比较简单,但是表示同步比较复杂
//信号量表示可用资源的个数
//一个信号量表示当前队列中元素的个数
//另一个信号量表示当前队列中空格的个数
//插入元素,消耗空格资源,释放元素资源,
//删除元素就是消耗了一个元素资源,释放了一个空格资源
//如果用信号量表示互斥,p操作和v操作在同一个函数
///如果用信号量表示同步,p操作和v操作在不同的函数中
//信号量这个计算器的+1 -1是原子的
template<typename T>
class BlockingQueue
{
public:
BlockingQueue(int max_size)
:max_size_(max_size)
,head_(0)
,tail_(0)
,size_(0)
,queue_(max_size)
{
sem_init(&lock_,0,1);//可用资源为1个 第二个参数为0 ,表示只在当前进程中,
sem_init(&elem_,0,0);
sem_init(&blank_,0,max_size);
}
~BlockingQueue()
{
sem_destroy(&lock_);
sem_destroy(&elem_);
sem_destroy(&blank_);
}
void Push(const T& data)
{
//每次要插入元素,就得先申请一个空格资源
//如果没有空格资源 (信号量为0),说明队列满了;
//满了就不能在继续插入,并且在push中阻塞
sem_wait(&blank_);//申请一个空格资源!!!!!!
sem_wait(&lock_);
queue_[tail_]=data;
++tail_;
++size_;
sem_post(&lock_);
sem_post(&elem_);//
}
//data表示出队列的这个元素
void Pop(T* data) //输出型参数
{
//每次出队列 先申请一个元素资源
//如果没有元素资源,队列为空
//就不能直接出队列,而需要在pop处位置阻塞
sem_wait(&elem_);
sem_wait(&lock_);
*data=queue_[head_];
++head_;
--size_;
sem_post(&lock_);
sem_post(&blank_);///释放空格
}
private:
std::vector<T> queue_;
int head_;
int tail_;
int size_;
int max_size_;
sem_t lock_;//可以用二元信号量表示互斥锁 (非0即1)表示互斥锁
sem_t elem_;//可用元素个数
sem_t blank_;//可用空格个数
};
///用BlockingQueue实现一个简单的生产者消费者模型
#include"BlockingQueue.hpp"
BlockingQueue <int>queue(100);
void* Product(void* arg)
{
(void)arg;
int count=0;
while(1)
{
queue.Push(++count);
usleep(789789);
}
return NULL;
}
void* Consume(void* arg)
{
(void)arg;
while(1)
{
int count=0;
queue.Pop(&count);
printf("count=%d\n",count);
// usleep(123123);
}
return NULL;
}
//进程间通信 管道 本质就是一个BlockingQueue
int main()
{
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,Product,NULL);
pthread_create(&tid2,NULL,Consume,NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
return 0;
}