消费者生产者模型:某个模块负责产生数据,这些数据由另一个模块来负责处理,产生数据的模块,形象地称为生产者,而处理数据的模块,就称为消费者,该模式还需有一个缓冲区处于生产者和消费者之间,作为一个中介,生产者把数据放入缓冲区,而消费者从缓冲区取出数据。
如图:
生产者消费者模型可分为:
1.三种关系:
生产者与生产者(互斥)
消费者与消费者(互斥)
生产者与消费者(互斥、同步)
2.两种角色:生产者 、消费者(一般都由特定的线程或进程承担)
3.一种交易场所:为生产者与消费者提供数据单元的缓冲区
生产者与消费者为什么不直接通信,为什么需要缓冲区?原因如下:
(1)解耦: 假设生产者和消费者分别是两个类,如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合),将来如果消费者的代码发生变化,可能会影响到生产者,而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低;
(2)支持并发:生产者直接调用消费者的某个方法,由于函数调用是同步的(阻塞的),在消费者的方法没有返回之前,生产者只好一直等待,若消费者处理数据很慢,生产者效率会很低,使用了生产者/消费者模式之后,生产者和消费者可以是两个独立的并发主体,生产者把制造出来的数据放到缓冲区,就可以再去生产下一个数据。基本上不用依赖消费者的处理速度;
(3)支持忙闲不均:若制造数据的速度时快时慢,缓冲区就有优势,当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中,等生产者的制造速度慢下来,消费者再慢慢消费。
以下用代码实现消费者、生产者模型:实现生产者生产一个,消费者消费一个,生产者不生产,消费者阻塞等待。
1.用互斥锁与条件变量实现,缓冲区用链表代替:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
typedef struct _Node
{
int data;
struct _Node* next;
}Node,*Node_p,**Node_pp;
Node_p head=NULL; //链表头结点
static Node_p Alloc_Node(int x)
{
Node_p node=(Node_p)malloc(sizeof(node));
if(node==NULL)
{
perror("malloc");
exit(0);
}
node->data=x;
node->next=NULL;
return node;
}
static int IsEmpty(Node_p head)
{
return head->next==NULL?1:0;
}
void PushHead(Node_p head,int x)
{
Node_p node=Alloc_Node(x);
node->next=head->next;
head->next=node;
}
void PopHead(Node_p head,int* data)
{
if(!IsEmpty(head))
{
Node_p del=head->next;
*data=del->data;
head->next=del->next;
free(del);
del=NULL;
}
}
void InitList(Node_pp head)
{
*head=Alloc_Node(0);
}
void Destroy(Node_p head)
{
int data=-1;
while(!IsEmpty(head))
{
PopHead(head,&data);
}
free(head);
head=NULL;
}
void Show(Node_p head)
{
Node_p cur=head->next;
while(cur)
{
printf("%d ",cur->data);
cur=cur->next;
}
printf("\n");
}
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER; //互斥锁
pthread_cond_t cond=PTHREAD_COND_INITIALIZER; //条件变量
//实现生产者消费者的互斥与同步:即生产者生产一个消费者消费一个
void* product(void* arg) //生产者
{
//sleep(2);
int data=-1;
while(1)
{
pthread_mutex_lock(&lock); //加锁
if(!IsEmpty(head)) //链表不为空
{
pthread_cond_wait(&cond,&lock); //条件变量阻塞等待消费者线程消费并释放当前获得锁
}
sleep(1); //每隔一秒生产一个
data=rand()%1234;
PushHead(head,data); //生产到链表头
printf("product done....# %d\n",data);
pthread_mutex_unlock(&lock); //生产完毕解锁
pthread_cond_signal(&cond); //并唤醒阻塞等待消费者线程消费
}
return NULL;
}
void* consume(void* arg) //消费者
{
int data=-1;
while(1)
{
pthread_mutex_lock(&lock); //加锁
if(IsEmpty(head)) //链表为空
{
pthread_cond_wait(&cond,&lock); //阻塞等待生产者线程生产并释放当前获得锁
}
PopHead(head,&data); //取走链表头部数据
printf("consume done....# %d\n",data);
pthread_mutex_unlock(&lock); //消费完解锁
pthread_cond_signal(&cond); //并唤醒阻塞等待的生产者线程生产
}
return NULL;
}
int main()
{
InitList(&head);
pthread_t consumer,producter;//创建生产者、消费者线程
pthread_create(&producter,NULL,product,NULL);
pthread_create(&consumer,NULL,consume,NULL);
//等待线程退出
pthread_join(consumer,NULL);
pthread_join(producter,NULL);
pthread_cond_destroy(&cond);//销毁条件变量
pthread_mutex_destroy(&lock); //销毁互斥锁
Destroy(head);
/* //测试链表代码
int i=0;
for(;i<10;++i)
{
PushHead(head,i);
Show(head);
}
for(;i>5;--i)
{
int data=-1;
PopHead(head,&data);
Show(head);
printf("data==%d\n",data);
}
*/
return 0;
}
结果如下:
2.信号量(无锁同步)实现消费者/生产者模型,缓冲区用环形队列(数组)表示:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#define MAX 64
int data[MAX]; //用数组模拟一个环形队列 :存取数据单元
sem_t blank_sem; //定义格子信号量,取值范围:MAX--->0(生产者需要的信号量)
sem_t data_sem; //定义数据信号量,取值范围:0---->MAX (消费者需要的信号量)
void* product(void* arg)
{
int i=0;
while(1)
{
sem_wait(&blank_sem); //P操作 ,若blank信号量为0则阻塞等待
sleep(1); //1.
if(i==MAX)
{
i=i%MAX; //若i等于MAX,则从头开始
}
data[i]=rand()%1234;
printf("product done...# %d\n",data[i]);
++i;
sem_post(&data_sem); //V操作 :释放data信号量
}
return NULL;
}
void* consume(void* arg)
{
//sleep(2); //2.
int i=0;
int value=0;
while(1)
{
sem_wait(&data_sem); //P操作,阻塞等待
if(i==MAX)
{
i=i%MAX;
}
value=data[i];
printf("consume done...# %d\n",value);
++i;
sem_post(&blank_sem); //V操作 :释放blank信号量
//sleep(1); //2.
}
return NULL;
}
int main()
{
//信号量初始化
sem_init(&blank_sem,0,MAX);
sem_init(&data_sem,0,0);
//定义生产者消费者线程
pthread_t consumer,producter;
pthread_create(&producter,NULL,product,NULL);
pthread_create(&consumer,NULL,consume,NULL);
pthread_join(consumer,NULL);
pthread_join(producter,NULL);
//销毁信号量
sem_destroy(&blank_sem);
sem_destroy(&data_sem);
return 0;
}
结果如下:
将以上代码可以使消费者先阻塞等待,生产者先将环形队列填满,这时消费者再每隔1s消费一个,此时效果如下:
消费者从环形队列第一个消费,生产者此时生产完一圈阻塞等待消费者消费一个,再去生产一个:
3.多生产者/消费者模型(2基础上加两把互斥锁)实现:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#define MAX 64
int data[MAX];
sem_t blank_sem;
sem_t data_sem;
//实现多消费者与多生产者模型则只需在前面单消费者生产者代码加两把互斥锁,以实现各自的互斥访问
pthread_mutex_t lock1=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2=PTHREAD_MUTEX_INITIALIZER;
void* product(void* arg)
{
static int i=0; //i设为静态变量,以实现多线程的共享
while(1)
{
sem_wait(&blank_sem);
pthread_mutex_lock(&lock1);
if(i==MAX)
{
i=i%MAX;
}
data[i]=rand()%1234;
printf("product done...# %d tid# %u\n",data[i],pthread_self());
++i;
sem_post(&data_sem);
sleep(1);
pthread_mutex_unlock(&lock1);
}
return NULL;
}
void* consume(void* arg)
{
static int i=0; //静态以实现多线程消费者共享,即一个消费者消费了一个数据,另一个线程则只能消费下一个
int value=0;
while(1)
{
sem_wait(&data_sem);
pthread_mutex_lock(&lock2);
if(i==MAX)
{
i=i%MAX;
}
value=data[i];
printf("consume done...# %d tid# %u\n",value,pthread_self());
++i;
sem_post(&blank_sem);
//sleep(1); //2.
pthread_mutex_unlock(&lock2);
}
return NULL;
}
int main()
{
sem_init(&blank_sem,0,MAX);
sem_init(&data_sem,0,0);
pthread_t consumer1,producter1;
pthread_t consumer2,producter2;
pthread_create(&producter1,NULL,product,NULL);
pthread_create(&producter2,NULL,product,NULL);
pthread_create(&consumer1,NULL,consume,NULL);
pthread_create(&consumer2,NULL,consume,NULL);
pthread_join(consumer1,NULL);
pthread_join(producter1,NULL);
pthread_join(consumer2,NULL);
pthread_join(producter2,NULL);
sem_destroy(&blank_sem);
sem_destroy(&data_sem);
pthread_mutex_destroy(&lock1);
pthread_mutex_destroy(&lock2);
return 0;
}
结果如下: