什么是生产者消费者模型?
生产者消费者问题是一个很经典的线程同步问题。将数据的产生和处理分由多个线程来完成,一部分用于完成数据的产生,另一部分用于完成数据的处理。我们形象地将产生数据的线程称为生产者,将用于数据的处理的线程称为消费者。生产者将数据生产出来,然后放入缓冲区,然后消费者从缓冲区去里边将数据取走。所以,当缓冲区里边是空的,即生产者没有生产数据的时候,消费者是没法进行消费的,同样的,当缓冲区里边放满了,生产者也是没法继续生产的,只有等消费者将数据取走留出空闲空间了才能继续生产。所以生产者与消费者之间是需要进行同步的。
相互关系
根据缓冲区的不同,生产者消费者之间的互斥关系的实现也就不同。如果说是基于类似于单边访问的(比如单链表),那么,生产者正在访问缓冲区(将数据写入缓冲区)的时候,消费者就不能访问缓冲区。如果说缓冲区是基于类似于循环队列,那么只需要按照一定的规则,生产者和消费者也是可以同时访问缓冲区的。
生产者-消费者模型的实现
缓冲区基于单链表
利用单链表来存储生产者生产出来的产品,生产者生产的过程实际就是往单链表里边插入数据的过程。虽然单量表看似没有上限,但是单链表的每一个节点都会耗费一定的空间,而空间是有限的,所以基于单链表的缓冲区也是有限的。
创建多个线程来充当消费者和生产者,通过将创建一个线程的过程加锁来实现对生产者与生产者之间的互斥,消费者与消费者之间的互斥。因为在创建的时候就会为其指定要执行的函数,并且一旦创建成功就会执行相应的函数,这里的函数就是生产或者消费的过程,保证对函数的访问的互斥,就可以实现对应角色之间的互斥了。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#define CONSUMERS 2
#define PRODUCERS 3
#define COUNT ((CONSUMERS) +(PRODUCERS))
pthread_mutex_t mutex,mutex_c,mutex_p;
pthread_cond_t cond;
pthread_t threads[COUNT];
typedef struct msg
{
int _data;
struct msg* _pNext;
}Node,*pNode;
pNode Head = NULL;
int Push_front(int data)
{
pNode newNode = (pNode)malloc(sizeof(struct msg));
if(NULL == newNode)
{
printf("创建新结点失败\n");
return 0;
}
newNode->_data = data;
newNode->_pNext = Head;
Head = newNode;
return 1;
}
int Pop_front()
{
pNode del = Head;
if(del == NULL)
return 0;
Head = del->_pNext;
free(del);
del = NULL;
return 1;
}
void* consum(void* arg) //消费者消费过程
{
int n = *(int*)arg;
free(arg);
pthread_mutex_lock(&mutex); //上锁
while(Head == NULL) //缓冲区里没有数据,基于条件变量进入等待
{
printf("there is nothing,please waitting\n");
pthread_cond_wait(&cond,&mutex);
}
printf("consumer%d : %d\n",n,Head->_data); //消费数据
Pop_front();
printf("consum end..\n",n);
pthread_mutex_unlock(&mutex); //解锁
}
void* product(void* arg)
{
sleep(1);
int n = *(int*)arg;
free(arg);
pthread_mutex_lock(&mutex); //上锁
printf("producter%d start to product %d\n",n,n);
Push_front(n); //生产数据
printf("product end..\n",n);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex); //解锁
}
int main()
{
pthread_mutex_init(&mutex,NULL);
pthread_mutex_init(&mutex_c,NULL);
pthread_mutex_init(&mutex_p,NULL);
pthread_cond_init(&cond,NULL);
//创建消费者线程
for(int i=0; i<CONSUMERS; i++)
{
int *p = (int*)malloc(sizeof(int));
*p = i;
pthread_create(&threads[i], NULL, consum, (void*)p);
}
//创建生产者线程
for(int i=0; i<PRODUCERS; i++)
{
int *p = (int*)malloc(sizeof(int));
*p = i;
pthread_create(&threads[CONSUMERS+i], NULL, product, (void*)p);
}
//主线程等待新线程
for(int i=0; i < COUNT; i++)
pthread_join(threads[i], NULL);
pthread_mutex_destroy(&mutex);
pthread_mutex_destroy(&mutex_c);
pthread_mutex_destroy(&mutex_p);
pthread_cond_destroy(&cond);
return 0;
}
缓冲区基于循环队列
由于当循环队列是空的和满的两种情况下生产者和消费指向的位置相同,为了方便我们判断缓冲区空还是满的状态,在这里使用浪费一个格子的空间的方式来区分满和空。
在对循环队列这段空间的访问形式是环状的,其实底层的实现是类似于数组(底层是一块连续的空间)的结构。只要保证对这块空间的访问遵循这样的规则,当将这段空间的最后一个元素访问完了,接着又继续回到这块空间开始的地方。
总结需要遵循的规则如下:
1、生产者始终走在消费者前边,必须先生产出来再消费
2、当缓冲区为空时,保证生产者先执行生产
3、当缓冲区满时,保证消费者先消费,否则生产者会将之前生产的还未被消费者拿走的数据覆盖掉。
下边利用两个信号量来实现生产者消费者模型
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/types.h>
#include <time.h>
int buf[20];
sem_t Blank;
sem_t Data;
void* consum(void* arg)
{
int out = 0;
while(1)
{
sem_wait(&Data);
int data = buf[out];
out++;
out %= 20;
printf("consum a data:%d\n",data);
sem_post(&Blank);
}
}
void* product(void* arg)
{
int in = 0;
while(1)
{
sem_wait(&Blank);
int data = rand()%100;
sleep(1);
buf[in] = data;
in ++;
in %= 20;
printf("product a data , is: %d\n",data);
sem_post(&Data);
}
}
int main()
{
sem_init(&Blank,0,20);
sem_init(&Data,0,0);
srand((unsigned long)time(NULL));//方便后边使用rand函数
pthread_t producer,consumer;
pthread_create(&producer,NULL,product,NULL);
pthread_create(&consumer,NULL,consum,NULL);
pthread_join(producer,NULL);
pthread_join(consumer,NULL);
sem_destroy(&Blank);
sem_destroy(&Data);
return 0;
}