生产者消费者模型是一个多线程同步问题的经典案例。
从上图简单来看A,B,C为生产者D,E为消费者,生产者呢把数据放入缓冲区,而消费者呢从缓冲区拿走数据。生产者与生产者,消费者与消费者之间呢是互斥的,生产者与消费者呢又是同步的。
简单分析下生产者消费者模型的好处:
生产者与消费者两个‘’种族‘’间的耦合度降低,也就是说生产者是否生产取决于缓存是否满,消费者消费取决于缓存是否空。大家呢是独立的,生产者呢不必每次生产豆看消费者是否消费,消费者每次消费不必等待生产者是否生产结束。大家的运行都取决于缓存这个中间媒介。
下面就简单来看下生产者消费者模型的具体实现。
1.信号量配合互斥锁实现
先简单的认识下信号量,我们对信号量有两种操作,wait(等待)和release(释放),当一个线程调用wait操作时,它要么得到资源然后将信号量减1,要么一直阻塞下去,直到信号量大于等于1是,也就是消费者想要消费必须有资源,不能透支,release(释放)对信号量加1。二者对应的操作如下。
int sem_wait(sem_t * sem);
int sem_post(sem_t * sem);
#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>
#include<stdlib.h>
#define MAX 6
pthread_mutex_t mutex;
int nready;
sem_t full; //放入数据的个数
sem_t empty; //有多少剩余空间
void* produce( void* arg )
{
int i = *(int*)arg;
while( 1 )
{
sem_wait(&empty); //需要有足够的空间
pthread_mutex_lock(&mutex);
printf("process %d is begin produce\n",i);
sleep(2);
nready++;
printf("process %d is end produce nready(%d)\n",i,nready);
pthread_mutex_unlock(&mutex);
sem_post(&full); //将放入数据个数+1
sleep(3);
}
}
void* custom( void* arg )
{
int i = *(int*)arg;
while( 1 )
{
sem_wait(&full); //要有数据
pthread_mutex_lock(&mutex);
printf("process %d is begin custom\n",i);
sleep(1);
--nready;
printf("process %d is end custom nready(%d)\n",i,nready);
pthread_mutex_unlock(&mutex);
sem_post(&empty); //让空间+1
sleep(1);
}
}
int main()
{
pthread_mutex_init(&mutex,NULL);
sem_init(&full,0,0);
sem_init(&empty,0,MAX);
pthread_t prs[2];
pthread_t cus[3];
int i;
for( i=0; i<2; i++ )
{
int* p = (int*)malloc(sizeof(int));
*p = i;
pthread_create(&prs[i],NULL,produce,(void*)p);
}
for( i=0; i<3; i++ )
{
int* p = (int*)malloc(sizeof(int));
*p = i;
pthread_create(&cus[i],NULL,custom,(void*)p);
}
for( i=0; i<3; i++ )
pthread_join(prs[i],NULL);
for( i=0; i<2; i++ )
pthread_join(cus[i],NULL);
pthread_mutex_destroy(&mutex);
}
试想一下,如果把sem_wait()和sem_post()放到pthread_mutex_lock()pthread_mutex_unlock()之间会如何呢?
答案是死锁,因为我们不能预知线程进入共享区顺序,如果消费者线程先对mutex加锁,并进入,sem_wait()发现队列为空,阻塞,而生产者在对mutex加锁时,发现已上锁也阻塞,双方永远无法唤醒对方。
2.条件变量加互斥锁`
pthread_mutex_lock(&mutex);
while( 条件为假 ) //担心wait被信号打断
pthread_cond_wait( &cond,&mutex );
修改条件
pthread_mutex_unclock(&mutex);
让条件满足的代码
pthread_mutex_lock(&mutex);
将条件设置成真
pthread_cond_signal( &cond );
//signal发送信息,如果没有wait,就会丢失。
pthread_mutex_unclock(&mutex);
为什么信号量在互斥区外,而条件变量在互斥区内呢?
因为互斥锁本质上是二元信号量,和信号量互斥的原理相同,而且放在互斥区会死锁,而条件变量是和互斥锁协同配合的。