在现实的软件开发过程中,经常会碰到如下情景:某个模块负责产生数据,这些数据由另外一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程和进程等)。产生数据的模块称之为生产者,而处理数据的模块就是消费者。
条件变量
在正式开始生产者与消费者模型之前,我们应该对条件变量有一个新的认识。
条件变量(Condition Variable):它的作用是描述资源的就绪状态,属于线程的一种同步机制。互斥锁用于上锁,条件变量用于等待。条件变量本身是由互斥量保护的,线程在改变之前首先会封锁互斥量,因此其他线程在获得互斥量之前不会觉察到这样的变化。
(1)pthread_cond_init 和 pthread_cond_destroy:条件变量的初始化与销毁。
#include <pthread.h> int pthread_cond_destroy(pthread_cond_t *cond);//条件变量销毁 int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);//条件变量初始化 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
参数cond:条件变量。
参数attr:条件变量的属性,设置为NULL则表示缺省属性。
返回值:成功返回0,失败返回错误码。
和mutex的初始化和销毁类似,如果条件变量是静态分配的,也可以使用PTHREAD_COND_INITALIZER初始化,相当于用pthread_cond_init函数初始化并设置参数attr为NULL。
(2)pthread_cond_wait/timewait:前者是条件变量等待,后者是等待超时。
#include <pthread.h> int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime); int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
参数cond:条件变量。
参数mutex:互斥锁。
返回值:成功返回0,失败返回错误码。
pthread_cond_timewait函数还有一个额外的参数可以设定等待超时,如果达到了abstime所指定的时刻仍然没有别的线程来唤醒当前线程,就返回ETIMEDOUT。
可见,一个条件变量总是和一个互斥锁搭配使用的。
一个线程可以调用pthread_cond_wait在一个条件变量上阻塞等待,这个函数需要以下三步操作:
1)释放mutex 2)阻塞等待 3)当被唤醒时,重新获得mutex并返回
(3)pthread_cond_broadcast/signal:唤醒线程。前者是唤醒进程内的所有线程,后者是唤醒一个线程。
#include <pthread.h> int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_signal(pthread_cond_t *cond);
返回值:成功返回0,失败返回错误码。
一个线程可以调用pthread_cond_signal唤醒在某个条件变量上等待的另一个线程,也可以调用pthread_cond_broadcast唤醒在这个条件变量上等待的所有线程。
生产者消费者模型
生产者与消费者牵扯最多的是多线程的同步问题,单单抽象出生产者和消费者,还够不上生产者消费者模型。因此该模式需要加入一个缓冲区作为二者的媒介,生产者把数据放入缓冲区,消费者从缓冲区取走数据,结构图如下:
生产者与消费者模型的321规则:3种关系,2个角色,一个交易场所(本文使用链表模拟实现)
这里我简单说一下三种关系:
(1)生产者与生产者(互斥)
(2)生产者与消费者(互斥+同步)
(3)消费者与消费者(互斥)
也就是说,生产者在进行生产的时候消费者不能消费,消费者在消费的时候生产者同样不能生产。缓冲区为空时,消费者不能消费,缓冲区满时,生产者也不能生产。
生产者消费者模型的三大特性
(1)解耦(软件工程追求高内聚低耦合)
假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(耦合)。将来如果消费者的代码发生了变化,可能会影响到生产者。但是如果二者均依赖于同一个方法(缓冲区),耦合度就会降低很多。
(2)支持并发(最主要的特性)
由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只能一直等待,如果消费者的处理速度很慢,生产者将会浪费很多时间。因此,生产者消费者模型就提出了缓冲区的概念,即生产者将数据放入缓冲区后,可以继续生产,将不再依赖于消费者的消费速度。
(3)支持忙闲不均
缓冲区的另一个好处在于生产数据的速度时快时慢,当数据生产的较快的时候,如果消费者来不及处理,未处理的数据就可以存放在缓冲区,消费者再慢慢进行处理。
基于单线程的生产者消费者模型
我们使用两个线程分别模拟了生产者和消费者,底层的数据结构借助于链表作为缓冲区,生产者通过向链表插入节点,消费者从链表中删除节点来模拟生产者与消费者的行为。
#include<stdio.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> #include<assert.h> pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//定义互斥锁 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//定义条件变量 typedef struct list { int _data; struct list* _next; }Node,*pNode; typedef struct linklist { Node *phead; }linklist,*plinklist; pNode CreatNode(int data) { pNode newNode = (pNode)malloc(sizeof(Node)); if(newNode == NULL) { perror("malloc"); exit(1); } newNode->_data = data; newNode->_next = NULL; return newNode; } void InitList(plinklist plist) { assert(plist); plist->phead = NULL; } void Push(plinklist list, int data) { assert(list); pNode newNode = CreatNode(data); if(list->phead == NULL) { list->phead = newNode; return; } newNode->_next = list->phead; list->phead = newNode; } void Pop(plinklist list,int *data) { assert(list); if(list->phead == NULL) { printf("list is empty!\n"); return; } pNode pDel = list->phead; *data = pDel->_data; list->phead = pDel->_next; pDel->_next = NULL; free(pDel); pDel = NULL; } void Destroy(plinklist list) { assert(list); if(list->phead != NULL) { pNode pCur = list->phead; while(pCur) { pNode pDel = pCur; pCur = pCur->_next; free(pDel); pDel = NULL; } } list->phead = NULL; } void *producter(void *arg)//生产者 { plinklist list = (plinklist)arg; while(1) { sleep(1); pthread_mutex_lock(&lock); Push(list,rand() % 100 + 1); pthread_cond_signal(&cond); pthread_mutex_unlock(&lock); printf("producter : %d\n",list->phead->_data); } } void *consumer(void *arg)//消费者 { plinklist list = (plinklist)arg; while(1) { sleep(1); pthread_mutex_lock(&lock); int data = 0; while(list->phead == NULL) { pthread_cond_wait(&cond, &lock); } Pop(list, &data); pthread_mutex_unlock(&lock); printf("consumer : %d\n",data); } } int main() { linklist list; InitList(&list); pthread_t tid1,tid2; pthread_create(&tid1, NULL, producter, (void*)&list); pthread_create(&tid2, NULL, consumer, (void*)&list); pthread_join(tid1, NULL); pthread_join(tid2, NULL); Destroy(&list); return 0; }
运行结果如下:
从结果可以看出,生产者生产一个,消费者消费一个,循环交替,实现了基于单线程的同步与互斥的生产者消费者模型。