什么是生产者消费者模型
在实际的开发中,经常会碰到如下场景:某个模块负责生产数据,某个模块负责处理这些数据。产生数据的模块就称为生产者,而处理数据的模块就称为消费者。这个模型还需要一个缓冲区来作为中介,生产者将产生的数据放入缓冲区中,消费者从缓冲区将数据取出并处理。
为什么需要生产者消费者模型
- 解耦
假设生产者和消费者是两个类,如果让生产者直接调用消费者的某个方法,那么生产者和消费者之间就产生依赖(耦合),此时不管是消费者还是生产者的代码发生改变,都会影响到对方。如果两者都依赖某个缓冲区而不直接依赖,耦合度降低。 - 支持并发
生产者直接调用消费者的某个方法还有一个弊端。由于函数调用是同步的(或者称作为阻塞的),在消费者的方法没有返回之前,生产者只好一直等待,如果消费者处理数据很慢,则生产者就会浪费大量时间在等待上面。使用生产者消费者模型后,生产者和消费者可以是两个独立的并发主体。生产者将生产出来的数据放入缓冲区后就可以继续生产下一个数据,不再依赖消费者的处理速度。 - 支持忙闲不均
如果生产数据的速度时快时慢,缓冲区的作用也就体现出来了,当生产数据很快,消费者还来不及处理,未处理的数据就会放入缓冲区中,等到生产者的速度慢下来后,消费者再慢慢处理。
生产者和消费者之间的关系
- 生产者和生产者之间是互斥关系
- 消费者和消费者之间是互斥关系
- 生产者和消费者之间是同步、互斥关系
也就是说:
- 一次只能有一个生产者生产,一个消费者消费。
- 生产者生产的时候消费者不能消费。
- 消费者消费的时候生产者不能生产。
- 缓冲区满时生产者不能生产。
- 缓冲区空时消费者不能消费。
生产者消费者模型实现方案
基于链表的生产者消费者模型,其空间可以动态分配。
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
pthread_mutex_t g_lock;
pthread_cond_t g_cond;
//先实现交易场所,使用一个链表来实现
//带头结点不带环的单向链表
//此时我们的链表就是一个栈
typedef struct Node{
int data;
struct Node* next;
}Node;
Node g_head;
Node*CreateNode(int value){
Node* ptr = (Node*)malloc(sizeof(Node));
ptr->data = value;
ptr->next = NULL;
return ptr;
}
void DestoryNode(Node* ptr){
free(ptr);
}
void Init(Node* head){
head->data = 0;
head->next = NULL;
}
void Push(Node* head, int value){
if(head == NULL){
return;
}
Node* new_node = CreateNode(value);
new_node->next = head->next;
head->next = new_node;
return;
}
void Pop(Node* head, int* value){
if(head == NULL){
return;
}
if(head->next == NULL){
//链表为空
return;
}
Node* to_delete = head->next;
head->next = to_delete->next;
*value = to_delete->data;
DestoryNode(to_delete);
}
//再实现来那个两种角色
void* Product(void* arg){
(void)arg;
int count = 0;
while(1){
pthread_mutex_lock(&g_lock);
Push(&g_head, ++count);
printf("Product %d\n", count);
pthread_mutex_unlock(&g_lock);
usleep(123456);
pthread_cond_signal(&g_cond);
}
return NULL;
}
void* Consume(void* arg){
(void)arg;
int count = -1;
while(1){
pthread_mutex_lock(&g_lock);
//pthread_cond_wait 有可能被信号打断
//当我们的pthread_cond_wait返回的时候,再次判断条件是否就绪
while(g_head.next == NULL){
pthread_cond_wait(&g_cond, &g_lock);
}
Pop(&g_head, &count);
printf("Consume %d\n", count);
usleep(789123);
pthread_mutex_unlock(&g_lock);
}
return NULL;
}
int main(){
pthread_cond_init(&g_cond, NULL);
pthread_mutex_init(&g_lock, NULL);
Init(&g_head);
pthread_t product, consume;
pthread_create(&product, NULL, Product, NULL);
pthread_create(&consume, NULL, Consume, NULL);
pthread_join(product, NULL);
pthread_join(consume, NULL);
pthread_mutex_destroy(&g_lock);
pthread_cond_destroy(&g_cond);
return 0;
}
基于固定大小的环形队列,利用信号量(POSIX版本的信号量)实现生产者消费者模型
初始化信号量:
#include<semaphore.h>
int sem_init(sem_t* sem,int pshared,unsigned int value);
//参数:
//pshared:0表示线程间共享,非零表示进程间共享
//value:信号量初始值
销毁信号量:
int sem_destroy(sem_t* sem);
等待信号量:
//功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t* sem);
发布信号量:
//功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t* sem);
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
////////////////////////////////////////////////////////////
//基于信号量实现生产者消费者模型
//POSIX版本的信号量
////////////////////////////////////////////////////////////
#include <semaphore.h>
sem_t g_lock;
sem_t g_data;
sem_t g_blank;
//先实现一个交易场所
//使用数组实现一个队列
#define SIZE 1024
typedef struct Queue{
int array[SIZE];
int head;
int tail;
int size;
}Queue;
Queue g_queue;
void Init(Queue* q){
q->head = q->tail = q->size = 0;
}
void Push(Queue* q, int value){
if(q->size >= SIZE){
return;
}
q->array[q->tail++] = value;
if(q->tail >= SIZE){
q->tail = 0;
}
++q->size;
}
void Pop(Queue* q, int * value){
if(q->size == 0){
return;
}
*value = q->array[q->head++];
if(q->head >= SIZE){
q->head = SIZE;
}
--q->size;
}
//再实现来那个两种角色
void* Product(void* arg){
(void)arg;
int count = 0;
while(1){
sem_wait(&g_blank);
sem_wait(&g_lock);
Push(&g_queue, ++count);
printf("Product %d\n", count);
sem_post(&g_lock);
usleep(789123);
sem_post(&g_data);
}
return NULL;
}
void* Consume(void* arg){
(void)arg;
int count = -1;
while(1){
sem_wait(&g_data);
sem_wait(&g_lock);
Pop(&g_queue, &count);
printf("Consume %d\n", count);
sem_post(&g_lock);
usleep(123456);
sem_post(&g_blank);
}
return NULL;
}
//再实现三种关系
int main(){
Init(&g_queue);
sem_init(&g_lock, 0, 1);
sem_init(&g_data, 0, 0);
sem_init(&g_blank, 0, SIZE);
pthread_t product, consume;
pthread_create(&product, NULL, Product, NULL);
pthread_create(&consume, NULL, Consume, NULL);
pthread_join(product, NULL);
pthread_join(consume, NULL);
sem_destroy(&g_lock);
sem_destroy(&g_data);
sem_destroy(&g_blank);
return 0;
}