生产者消费者概念
在实际的开发中,通常会碰到一个场景,有一个模块产生数据被称为生产者,另一个模块来接受数据被称为消费者(此处的模块是指广义的模块 函数,类,进程等),但是有生产者和消费者的场景还不能称为生产者消费者模型,还需要一个缓冲区在生产者和消费者之间作为中介,生产者把数据放到缓冲区中,消费者从缓冲区中取数据。
这个关系类似生活中我们到超市买东西,供货商将商品送到超市,我们就是消费者,供货商就是生产者,超市就是中间的缓冲区。
生产者消费者模型优点
具体优点可分为以下几点
1.解耦
假设生产者和消费者是两个类,消费者直接调用生产者,这两个类就是产生了耦合,假如生产者改变,消费者也会产生影响,当生产者和消费者中间有一层缓冲区,两者不会直接依赖,耦合度就降低了。
2.支持并发操作
消费者调用生产者,还有另一个问题,因为函数调用时同步的,在生产者没有返回数据单位前,消费者只能在旁边等待,浪费时间。
在使用生产者/消费者模式时,生产者和消费者是两个独立的并发主体,生产者不停的将数据送进缓冲区中,基本不用考虑消费者的问题。
这个模式主要就是解决消费实例的问题的。
3.支持忙闲不均
缓冲区还有另一个好处,当生产者产生数据时快时慢,消费者来不及处理,可以将数据放在缓冲区中,等生产者的速度慢下来,消费者再慢慢处理。
数据单元
简单的说,每次生产者放入缓冲区的数据,就是一个数据单元,消费者收到的数据也是一个数据单元
数据单元的特性
1.关系到业务对象
生产者消费者模型必须要关系到某种业务对象,在考虑问题时必须对生产者消费者模型的业务逻辑考虑清楚才能确定数据单元。
如果选择错误业务对象,就会导致程序设计和代码实习的复杂程度增加,提高开发和维护的成本。
2.完整性
所谓的完整性是指,在数据传递的过程中,要保持数据单元的完整性,生产者要么将一个数据单元发给消费者,或者不发,没有发给半个数据单元给消费者的情况。
类比寄信,往邮局进行必须给一封完整的信,否则就不给,没有给半封的道理。
3.独立性
所谓独立性指的是各个数据单元相互独立,发送失败的数据单元不应该影响已发送的数据单元更不应该影响未发送的数据单元。
假设一种场景,假如生产者的生产数据单元的速度比消费者的处理速度,那么数据会不断堆积到缓冲区中,直到把缓冲区占满,新的数据单元无法传进去,假如生产者和消费者相互独立,耦合度较低,等生产者的速度降下来,新传入的数据单元不应该被影响,反之,如果两者间有耦合,失败的数据单元会影响新传入的数据单元,代码会变复杂。
4.颗粒度
前面说到,数据单元和业务对象有关,在某些时候他们是一一对应的,但是有的时候,可能会把N个业务对象打包,变成一个数据单元。
举个例子,例如美团外卖,一份订单相当于一个业务对象,假如外卖员每接到一个订单就立刻去送(颗粒度为1),会损失潜在的其他订单的收益,假如接到100个订单后才送餐(颗粒度为100),时间上又会浪费。
所以当颗粒度过小会造成性能上的损失,颗粒度过大又会造成某种资源的浪费(内存,套接字)。
123原则
在生产者消费者模型,可以用123原则记住三种关系,两种对象,一个中介。
- 三种关系,生产者之间的互斥,消费者之间的互斥,消费者和生产者之间的互斥和同步。
- 两种对象,生产者和消费者
- 一个中介,缓冲区。
如果有读者对于互斥和同步不太清楚,可以看这篇博客浅谈互斥和同步,可能需要一个梯子,这里就不多说了。
基于阻塞队列的单生产者单消费者
为了实现简单,下面的代码用队列来实现,为了防止运行过快,用sleep函数,使得1秒运行一次,使用线程运行两个代码,用pthread_cond和pthread_mutex来进行通信,这里的queue是共享资源。
#include <iostream>
#include <queue>
#include <stdlib.h>
#include <unistd.h>
using namespace std;
#define NUM 8
class BlockQueue
{
private:
queue<int> q;
int cap;//容量
pthread_mutex_t lock;
pthread_cond_t full;
pthread_cond_t empty;
public:
BlockQueue(int _cap =NUM):cap(_cap)
{
pthread_mutex_init(&lock,NULL);
pthread_cond_init(&full,NULL);
pthread_cond_init(&empty,NULL);
}
~BlockQueue()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&full);
pthread_cond_destroy(&empty);
}
void pushData(const int& data)
{
pthread_mutex_lock(&lock);
while(q.size()== cap)
{
pthread_cond_signal(&full);
cout<<"queue is full ,stop porduct"<<endl;
pthread_cond_wait(&empty,&lock);
}
q.push(data);
pthread_mutex_unlock(&lock);
}
void popData(int & data)
{
pthread_mutex_lock(&lock);
while(q.size()== 0)
{
pthread_cond_signal(&empty);
cout<<"queue is empty ,stop consumer"<<endl;
pthread_cond_wait(&full,&lock);
}
q.pop();
data = q.front();
pthread_mutex_unlock(&lock);
}
};
void* producter(void* arg)
{
int data;
srand((unsigned long)time(NULL));
for(;;)
{
BlockQueue *bq = ( BlockQueue*) arg;
data = rand()%1024;
bq->pushData(data);
cout<<"the data is push done :"<<data<<endl;
sleep(1);
}
}
void* consumer(void *arg)
{
BlockQueue *bq = ( BlockQueue*) arg;
int data;
for(;;)
{ bq->popData(data);
cout<<"the data is pop done :"<<data<<endl;
sleep(1);
}
}
int main()
{
BlockQueue bd;
pthread_t c,p;
pthread_create(&c,NULL,consumer,(void*)&bd);//线程为可分离的
pthread_create(&p,NULL,producter,(void*)&bd);
pthread_join(c,NULL);
pthread_join(p,NULL);
}
总结
生产者消费者模型包括生产者,消费者,缓冲区。
生产者将数据单位存到缓冲区中,消费者从缓冲区中拿数据单位。两者并行独立,耦合度低,假如有一方的实现改变,不会影响另一方,支持忙闲不均,当生产者的生产速度超过消费者的处理速度,可以把数据单位放在缓冲区中,暂时保存。
数据单位业务逻辑有关具有完整性,颗粒度,独立性的特性。
上面的代码是根据阻塞队列原理产生的生产者消费者模型,在下篇博客中,我们将基于信号实现生产者消费者模型。