“生产者”和“消费者”问题是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区已经装满时加入数据,消费者也不会在缓冲区为空时消耗数据。
假定在生产者和消费者之间的公用缓冲池中具有n个缓冲区,这时可利用互斥信号量mutex实现诸进程对缓冲池的互斥使用:利用信号量empty和full分别表示缓冲池中空缓冲区和满缓冲区的数量。又假定这些生产者和消费者相互等效,只要缓冲池未满,生产者便可将消息送入缓冲池;只要缓冲池未空,消费者便可从缓冲池中取走一个消息。PV原语算法如下所示:
实现代码如下:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#define BUFSIZE 10
#define PRODUCER_NUM 3
#define CONSUMER_NUM 3
sem_t full;
sem_t empty;
sem_t mutex;
//定义缓冲区
int buf[BUFSIZE];
//队尾
int in = 0;
//队头
int out = 0;
void printbuf()
{
for(int i=0; i<BUFSIZE; i++)
{
printf("%d", buf[i]);
}
printf("\n");
}
//生产者线程处理函数
void* producer(void* arg)
{
int number_p = *((int *)arg);
while(1)
{
sem_wait(&empty);
sem_wait(&mutex);
buf[in] = 1;
printf("生产者%d生产产品:%d buf:", number_p, in);
printbuf();
in = (in + 1)%BUFSIZE;
sem_post(&full);
sem_post(&mutex);
sleep(rand()%3);
}
return NULL;
}
//消费者线程处理函数
void* consumer(void* arg)
{
int number_c = *((int *)arg);
while(1)
{
sem_wait(&full);
sem_wait(&mutex);
buf[out] = 0;
printf("消费者%d消费产品:%d buf:", number_c, out);
printbuf();
out = (out + 1)%BUFSIZE;
sem_post(&empty);
sem_post(&mutex);
sleep(rand()%3);
}
return NULL;
}
int main()
{
pthread_t tid[PRODUCER_NUM+CONSUMER_NUM];
//创建三个信号量
sem_init(&full, 0, 0);
sem_init(&empty, 0, BUFSIZE);
sem_init(&mutex, 0, 1);
int* number = malloc(sizeof(int) * (PRODUCER_NUM+CONSUMER_NUM));
for(int i=0; i<PRODUCER_NUM+CONSUMER_NUM; i++)
{
if(i < PRODUCER_NUM)
{
//创建生产者线程
number[i] = i;
pthread_create(&tid[i], NULL, producer, (void *)(number+i));
}
else
{
//创建消费者线程
number[i] = i-PRODUCER_NUM;
pthread_create(&tid[i], NULL, consumer, (void *)(number+i));
}
}
//回收线程
for(int i=0; i<PRODUCER_NUM+CONSUMER_NUM; i++)
{
pthread_join(tid[i], NULL);
}
//销毁信号量
sem_destroy(&full);
sem_destroy(&empty);
sem_destroy(&mutex);
free(number);
return 0;
}
主要使用的函数接口是信号量的初始化和销毁,以及P和V操作,定义如下:
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:
创建一个信号量并初始化它的值。一个无名信号量在被使用前必须先初始化。
参数:
sem:信号量的地址。
pshared:等于 0,信号量在线程间共享(常用);不等于0,信号量在进程间共享。
value:信号量的初始值。
返回值:
成功:0
失败: - 1
int sem_destroy(sem_t *sem);
功能:
删除 sem 标识的信号量。
参数:
sem:信号量地址。
返回值:
成功:0
失败: - 1
int sem_wait(sem_t *sem);
功能:
将信号量的值减 1。操作前,先检查信号量(sem)的值是否为 0,若信号量为 0,此函数会阻塞,直到信号量大于 0 时才进行减 1 操作。
参数:
sem:信号量的地址。
返回值:
成功:0
失败: - 1
int sem_post(sem_t *sem);
功能:
将信号量的值加 1 并发出信号唤醒等待线程(sem_wait())。
参数:
sem:信号量的地址。
返回值:
成功:0
失败:-1