这是进程/线程同步与互斥中最简单、最经典的一个例子,也被叫做“有限缓冲区”问题。
假定现在有一个“有限缓冲池”(最讨厌这种fancy word-_-+,其实就是空间有限的一块地方吧),生产者负责往里面投放资源,消费者负责把资源拿走(这样的模型很常见,比如说打字,你就是一个生产者,负责往IO缓冲区写东西,处理这个缓冲区的程序就是消费者,负责把你输入的东西拿出来显示到屏幕上)。这里会出现几个问题:
1)缓冲区空了,则消费者不能继续读
2)缓冲区满了,则生产者不能继续写
解决的办法是:
1)如果缓冲区空了,我们应该让消费者停下来
2)如果缓冲区满了,应该让生产者停下来
(这不是废话吗。。。。。。。。。。。
可是不管是消费者还是生产者都不能一直停着不干活啊,所以核心问题就是实现同步:当一个停下的时候,另一个要在适当地时候唤醒它。
下面来看一个用条件变量和互斥锁来演示消费者生产者问题的例子:
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
struct msg{
struct msg* next;
int num;
};
struct msg* head;
pthread_cond_t has_product=PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
void* producer(void* p){
struct msg* mp;
int i;
for(i=0;i<20;i++){
mp=malloc(sizeof(struct msg));
mp->num=rand()%1000+1;
printf("Produce %d\n",mp->num);
pthread_mutex_lock(&lock);
mp->next=head;
head=mp;
pthread_mutex_unlock(&lock);
pthread_cond_signal(&has_product);
sleep(rand()%5);
}
}
void* consumer(void* p){
struct msg* mp;
int i;
for(i=0;i<20;i++){
pthread_mutex_lock(&lock);
while(head==NULL)
pthread_cond_wait(&has_product,&lock);
mp=head;
head=mp->next;
pthread_mutex_unlock(&lock);
printf("Consume %d\n",mp->num);
free(mp);
sleep(rand()%5);
}
}
int main(char argc,char* argv[]){
pthread_t pt,ct;
srand(time(NULL));
pthread_create(&pt,NULL,producer,NULL);
pthread_create(&ct,NULL,consumer,NULL);
pthread_join(pt,NULL);
pthread_join(ct,NULL);
return 0;
}
运行效果截图:
这个问题也可以用信号量来说明,代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#include<time.h>
sem_t full,empty;
pthread_mutex_t mutex;
#define BUFFERSIZE 5
struct msgbuf{
pid_t id;
time_t mytime;
};
struct msgbuf msg[BUFFERSIZE];
int in=0,out=0;
void* producer(void* arg){
int i;
time_t rt;
for(i=1;i<=10;i++){
sem_wait(&empty);
pthread_mutex_lock(&mutex);
msg[in].id=pthread_self();
time(&msg[in].mytime);
in=(++in)%5;
printf("生产者%d第%d次写消息:id=%u,time is %s\n",\
arg,i,(unsigned)(msg[out].id),ctime(&msg[out].mytime)
);
pthread_mutex_unlock(&mutex);
sem_post(&full);
srand((unsigned)time(&rt));
sleep(rand()%5);
}
}
void* consumer(void* arg){
int i;
time_t rt;
for(i=1;i<=10;i++){
sem_wait(&full);
pthread_mutex_lock(&mutex);
printf("消费者%d第%d次读消息:id=%u,time is %s\n",\
arg,i,(unsigned)(msg[out].id),ctime(&(msg[out].mytime))
);
out=(++out)%5;
pthread_mutex_unlock(&mutex);
sem_post(&empty);
srand((unsigned)time(&rt));
sleep(3+rand()%5);
}
}
int main(char argc,char* argv[]){
pthread_t pid1,pid2;
pthread_t cid1,cid2;
sem_init(&full,0,0);
sem_init(&empty,0,5);
pthread_mutex_init(&mutex,NULL);
pthread_create(&pid1,NULL,producer,NULL);
pthread_create(&pid2,NULL,producer,NULL);
pthread_create(&cid1,NULL,consumer,NULL);
pthread_create(&cid2,NULL,consumer,NULL);
pthread_join(pid1,NULL);
pthread_join(pid2,NULL);
pthread_join(cid1,NULL);
pthread_join(cid2,NULL);
pthread_mutex_destroy(&mutex);
sem_destroy(&full);
sem_destroy(&empty);
return 0;
}
其中full表示消息的个数,empty表示空缓冲区个数。初始时full=0,empty=5,表示有0个消息,5个空缓冲区。信号量操作主要有P和V两个,P表示申请资源。对应sem_wait,V表示释放资源,对应sem_post。本质上来说,它们对信号量的value进行减操作或者加操作,根据结果判断要不要阻塞或者唤醒。(具体原理这里就略过了)
效果: