生产者消费者模型(PC模型)
生产者消费者模型,其实已经很熟悉了,但真正让程序把它跑起来,这里面还有不少细节需要我们注意。
生产者消费者模型,可以归结为一个123,即1个场所,2个对象,3种关系。
它的过程就是 一个生产者生产数据放进缓冲池,消费者从缓冲池中取数据消费,在这期间,生产者生产数据和消费者消费数据两者不能同时进行,即放的时候不能拿,拿的时候不能放,必须是其中一个操作结束之后,才可以进行下一步操作。这样生产者和消费者就形成了一种互斥的关系;
而在缓冲池空的时候,消费者必须先等生产者生产之后,他才可以取数据;同样,当缓冲池满的时候,生产者必须等大消费者把数据拿走之后,自己才可以继续生产,这种关系我们把它称之为同步。所以,生产者和消费者之间就形成了同步于互斥的这种关系。
而另外两种关系就是生产者和生产者以及消费者和消费者之间的互斥关系。
而当我们编写程序时,要实现这两种关系,就要用到互斥量+条件变量+锁;当然,如果也可以用互斥量+信号量来实现。我们今天只实现第一种方法。
之所以,第一种方法要加锁,就是因为我们的生产者和消费者在存取数据的操作都是非原子操作,也就是他们的操作可以被打断。比如说,一个消费者在取数据时,取完之后,那么他就要更新此时缓冲是池里面数据的个数,而这时,生产者又放了一个数据,那么接下来消费者再更新个数后,显然数据是不对的。所以,我们就要将他们的操作全部用锁保护起来,让一个执行完下一个才可以执行。
在写程序之前,我们必须先熟悉各个函数的接口和功能。不了解请看之前写的博客—>程序所用到的接口
有了这些接口后,我们就可以模拟这个过程了。
场景
有一个消费者,三个生产者,缓冲池大小为8,请模拟实现pc模型
过程我就不解释了,代码有注释。
#include <iostream>
#include <queue>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
using namespace std;
#define NUM 8
//存放数据的缓冲池
class BlockQueue
{
private:
std::queue<int> q;
int cap;//缓冲池的容量大小
pthread_mutex_t lock;
pthread_cond_t full;
pthread_cond_t empty;
int pthread_pro_sum;//生产者线程总数
int pthread_con_sum;//消费者线程总数
bool is_quit;
private:
void LockQueue()
{
pthread_mutex_lock(&lock);
}
void UnLockQueue()
{
pthread_mutex_unlock(&lock);
}
void ProductWait() //生产者睡眠
{
if(is_quit)
{
pthread_pro_sum--;
UnLockQueue();//解锁
cout<<"一个生产者线程退出..."<<endl;
pthread_exit((void*)0);
}
pthread_cond_wait(&full, &lock);
}
void ConsumeWait() //消费者睡眠
{
if(is_quit)
{
pthread_con_sum--;
//解锁
UnLockQueue();
cout<<"一个消费者线程退出..."<<endl;
pthread_exit((void*)0);
}
pthread_cond_wait(&empty, &lock);
}
void NotifyProduct() //唤醒一个生产者
{
pthread_cond_signal(&full);
}
void NotifyConsume() //唤醒一个消费者
{
pthread_cond_signal(&empty);
}
bool IsEmpty()
{
return ( q.size() == 0 ? true : false );
}
bool IsFull()
{
return ( q.size() == cap ? true : false );
}
public:
BlockQueue(int _cap = NUM)
:cap(_cap)
,is_quit(false)
,pthread_con_sum(0)
,pthread_pro_sum(0)
{
pthread_mutex_init(&lock, NULL);
pthread_cond_init(&full, NULL);
pthread_cond_init(&empty, NULL);
}
void PushData(const int &data) //生产数据
{
LockQueue();
while(IsFull()){
// NotifyConsume();
std::cout << "Queue is full,Product is waiting." << std::endl;
ProductWait();
}
q.push(data);
NotifyConsume();
UnLockQueue();
}
void PopData(int &data) // 消费数据
{
LockQueue();
while(IsEmpty()){
// NotifyProduct();
std::cout << "Queue is empty,Consume is waiting." << std::endl;
ConsumeWait();
}
data = q.front();
q.pop();
NotifyProduct();
UnLockQueue();
}
void Stop() //结束生产消费过程
{
LockQueue();
is_quit = true;
UnLockQueue();
if(pthread_con_sum > 0)
{
cout<<"Consumer thread is:"<<pthread_con_sum<<endl;
pthread_cond_broadcast(&empty);
}
if(pthread_pro_sum > 0)
{
cout<<"Producter thread is:"<<pthread_pro_sum<<endl;
pthread_cond_broadcast(&empty);
}
cout<<endl;
return ;
}
bool Quit() //判断线程是否全部退出
{
if(pthread_con_sum == 0 && pthread_pro_sum == 0)
return true;
else return false;
}
void SetConsumSum(int n) //设置消费者线程总数
{
LockQueue();
pthread_con_sum = n;
UnLockQueue();
}
void SetProductSum(int n) //设置生产者线程总数
{
LockQueue();
pthread_pro_sum = n;
UnLockQueue();
}
~BlockQueue()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&full);
pthread_cond_destroy(&empty);
}
};
void *consumer(void *arg) //消费者
{
pthread_detach(pthread_self());
BlockQueue *bqp = (BlockQueue*)arg;
int data;
for( ; ; ){
bqp->PopData(data);
std::cout << "Consume data done : " << data << std::endl;
sleep(1);
}
}
void *producter(void *arg) //生产者
{
pthread_detach(pthread_self());
BlockQueue *bqp = (BlockQueue*)arg;
srand((unsigned long)time(NULL));
for( ; ; ){
int data = rand() % 1024;
bqp->PushData(data);
std::cout << "Prodoct data done: " << data << std::endl;
sleep(1);
}
}
int main()
{
BlockQueue bq;
pthread_t c;
//创建一个消费者线程
if(pthread_create(&c, NULL, consumer, (void*)&bq)!=0)
{
cout<<" create faild"<<endl;
return 1;
}
else
bq.SetConsumSum(1);
//创建3个生产者线程
for(int i=0;i<3;i++)
{
pthread_t p;
if(pthread_create(&p, NULL, producter, (void*)&bq)!=0)
{
cout<<"create p faild"<<endl;
return 1;
}
}
bq.SetProductSum(3);
// 过程执行10秒,线程开始退出
sleep(10);
cout<<"线程开始退出..."<<endl;
bq.Stop();
//等待所有线程退出,主进程再退出
while(1)
{
if(bq.Quit())
{
cout<<"thread is all quit"<<endl;
break;
}
}
//所有线程都退出后,等5秒主进程退出
cout<<"所有线程全部退出..."<<endl;
sleep(5);
// pthread_join(c, NULL);
// pthread_join(p, NULL);
return 0;
}
结果分析:
1.先让程序跑起来
2.先执行消费者进程,所以第一句是队列为空,消费者等待,接下来就是三个生产者放数据,一个消费者取数据。当放了8个之后,生产者就处于睡眠状态。
3.过程持续10秒之后,让消费者和生产者都开始陆续退出,注意,这里线程退出一定是该线程已经处于睡眠状态,也就是说此时睡眠的线程就不用等了,直接退出,而没有睡眠的线程会继续执行完当前操作,下次存取数据时,会再进行判断缓冲池,如果阻塞那么就退出,否则,就绪执行。
5.因为生产数据的速度是比消费的快,所以一定会有缓冲池慢的情况,当只剩下一个消费者和一个生产者的时候,生产者再生产一个数据,就满了,所以此时最后一个生产者退出后,消费者取走全部的8个数据,也就退出了。