问题描述
有多个线程:多个生产者线程和一个消费者线程程共享一个初始为空、固定大小为maxbuf的缓存(缓冲区)。多个生产者的工作是向缓冲区中存数据,只有缓冲区没满时,每次只能选择一个生产者把消息放入到缓冲区,否则必须等待,如此反复; 同时,只有缓冲区不空时,消费者才能从中取出消息,一次消费一个数据(即将其从缓存中移出),否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或者一个消费者从中取出消息。
问题分析
在前面的单个生产者&单个消费者问题中,我们使用了三个信号量来保证生成者与消费者的互斥。
对于生产者:
sem_wait(shared.empty); //empty--
sem_wait(shared.mutex); //获得互斥锁
....
sem_post(shared.mutex); //释放互斥锁
sem_post(shared.full); //full++
上面的代码可以保证每次只有一个生产者进入临界区,并且与消费者互斥访问。
对于消费者:
sem_wait(shared.full); //full--
sem_wait(shared.mutex); //获得互斥锁
....
sem_post(shared.mutex); //释放互斥锁
sem_post(shared.empty); //empty--
上面的代码可以保证生产者与消费者之间的互斥。
通过上面的分析,我们发现之前的三个信号量就可以解决该问题,我们可以尝试用之前的代码框架来解决该问题。
问题解决(基于内存的信号量)
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<semaphore.h>
#define maxbuf 5
#define pthread_n 4
using namespace std;
struct Shared
{
int buf[maxbuf];
int next_in;
sem_t mutex,empty,full;
};
Shared shared;
void *produce(void *arg){
for(;;){
sem_wait(&shared.empty);
sem_wait(&shared.mutex);
if(shared.next_in >= maxbuf){
sem_post(&shared.mutex);
return NULL;
}
if(shared.buf[shared.next_in] == 0){
shared.buf[shared.next_in] = shared.next_in;
printf("Producer %d Successfully produce item %d\n",*((int *)arg),shared.next_in);
}
shared.next_in++;
sem_post(&shared.mutex);
sem_post(&shared.full);
}
}
void *consume(void *arg){
for(int x=0;x<maxbuf;x++){
sem_wait(&shared.full);
sem_wait(&shared.mutex);
if(shared.buf[x]!=0){
printf("Comsumer 0 Successfully consume item %d\n",shared.buf[x]);
shared.buf[x] = 0;
}
sem_post(&shared.mutex);
sem_post(&shared.empty);
}
}
/*const char *m = "/mutex";
const char *e = "/empty";
const char *f = "/full";*/
int main(){
pthread_t p_tid[pthread_n+1];
int count[pthread_n];
shared.next_in = 0;
/* shared.mutex = sem_open(m,O_CREAT | O_EXCL,S_IRUSR | S_IWUSR, 1);
shared.full = sem_open(f,O_CREAT | O_EXCL,S_IRUSR | S_IWUSR, 0);
shared.empty = sem_open(e,O_CREAT | O_EXCL,S_IRUSR | S_IWUSR, maxbuf);*/
sem_init(&shared.mutex,0, 1);
sem_init(&shared.full,0, 0);
sem_init(&shared.empty,0, maxbuf);
for(int x=0;x<pthread_n;x++){
count[x] = x;
pthread_create(&p_tid[x],NULL,&produce,&count[x] );
}
pthread_create(&p_tid[pthread_n],NULL,&consume,NULL);
for(int x=0;x<=pthread_n;x++){
pthread_join(p_tid[x],NULL);
}
sem_destroy(&shared.mutex);
sem_destroy(&shared.full);
sem_destroy(&shared.empty);
}
运行结果:
看起来好像运行正常,没什么问题。
我们修改一下创建的线程数量:4改为6(生产者数量大于缓冲区数量)
运行之后发现,程序陷入了死锁。所以原来的框架还是有问题。
修改
原来生产者线程中的代码:
sem_wait(&shared.empty);
sem_wait(&shared.mutex);
if(shared.next_in >= maxbuf){
sem_post(&shared.mutex);
return NULL;
}
if(shared.buf[shared.next_in] == 0){
shared.buf[shared.next_in] = shared.next_in;
printf("Producer %d Successfully produce item %d\n",*((int *)arg),shared.next_in);
}
shared.next_in++;
sem_post(&shared.mutex);
sem_post(&shared.full);
添加一行代码:
sem_wait(&shared.empty);
sem_wait(&shared.mutex);
if(shared.next_in >= maxbuf){
sem_post(&shared.mutex);
sem_post(&shared.empty); //退出前应该将empty的值+1
return NULL;
}
if(shared.buf[shared.next_in] == 0){
shared.buf[shared.next_in] = shared.next_in;
printf("Producer %d Successfully produce item %d\n",*((int *)arg),shared.next_in);
}
shared.next_in++;
sem_post(&shared.mutex);
sem_post(&shared.full);
运行结果:
运行结果正常。
分析
我们来分析一下上面出现死锁的结果。
死锁时,shared.next_in的值为5,此时每个生产者线程都执行下面的代码:
if(shared.next_in >= maxbuf){
sem_post(&shared.mutex);
// sem_post(&shared.empty);
return NULL;
}
此时由于生产者数量大于缓冲区个数,在进入临界区之前就已经将empty的值减一,但是此时并没有向缓存区生产数据,就会使得出现empty的值小于0的情况,从而造成死锁。因此返回时,得将empty的值加一。