生产者-消费者问题是进程间或线程间同步的经典问题。该问题有多种实现,如消息队列、共享内存等,这次我们用的是多线程和互斥锁以及条件变量来实现该问题。
动手写程序之前先得把问题想明白。
1.该问题难在什么地方?
难点其实只有一个,就是同步问题。这个同步涉及了以下几个方面:多个生产者之间的同步(如:生产资源的总数的同步,生产的资源不会乱序的同步)、多个消费者之间的同步(如:消费资源的总数的同步,现有资源数目的同步)、生产者与消费者之间的同步(如:若现有资源为0,消费者要等生产者生产后才能继续消费)
2.如何解决上述的同步问题?
解决方法就是使用互斥锁和条件变量。如何使用呢?
1.可以将生产的资源相关信息和互斥锁A捆绑在一起,这样生产者生产之前先获取互斥锁A,再进行生产,更新资源信息。这便解决了生产者间的同步问题。
2.可以将消费资源相关信息和互斥锁B捆绑在一起,这样消费者消费之前先获取互斥锁B,再进行消费,更新消费信息。这便解决了消费者间的同步问题。
3.生产者和互斥锁直接的同步呢?这就是条件变量发挥作用的地方。消费者消费时,若发现现有资源数为0,则使用条件变量等待生产者生产资源,生产者生产资源后,便可通过条件变量通知消费者继续消费。
3.代码
要实现的过程:
1.创建生产者线程进行生产,若生产总数达到要求,则停止生产,终止生产者线程。
2.创建消费者线程进行消费,若已消费资源总数为要求生产总数,且现有资源数为0,则停止消费,终止消费者线程。若只是资源数为0,则等待生产者生产后继续消费。
3.生产者只有在现有资源数从0变为1才通知消费者消费;消费者也只在现有资源数为0时才等待生产者生产。
生产任务是对buff数组按顺序赋值,比如buff[i] = i。消费任务就是将现有资源数减1。这些都可以换成其他任务。
#include <stdio.h>
#include <pthread.h>
/* 任务数 */
#define MAXITEMS 1000000
/* 生产者线程数 */
#define MAXPRODUCE 100
/* 消费者数目 */
#define MAXCONSUME 10
typedef struct shared_t
{
pthread_mutex_t mutex; //互斥锁
//任务
int buff[MAXITEMS]; //任务数组
int nput; //已生产资源数
int nval; //任务值
}put;
put task = {PTHREAD_MUTEX_INITIALIZER};
typedef struct nready_t //若生产者生产的资源数为0,则阻塞消费者
{
pthread_mutex_t mutex;
pthread_cond_t cond;
int n; //可消费资源数目
int eat; //已消费数目
}nready;
nready ready = {PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER};
void *produce(void *arg); //生产程序
void *consume(void *arg); //消费程序
int main()
{
int i;
int count[MAXPRODUCE]; //每个生产者已生产资源数目
pthread_t tid_produce[MAXPRODUCE]; //生产者线程ID
pthread_t tid_consume[MAXCONSUME]; //消费者线程ID
pthread_setconcurrency(MAXPRODUCE + MAXCONSUME); //设置并发线程数
for (i = 0; i < MAXPRODUCE; i++)
{
count[i] = 0;
pthread_create(&tid_produce[i], NULL, produce, (void *)&count[i]); //创建并启动生产者线程
}
for (i = 0; i < MAXCONSUME; i++)
pthread_create(&tid_consume[i], NULL, consume, NULL); //创建并启动消费者线程
int total_produce = 0; //总任务数
for (i = 0; i < MAXPRODUCE; i++)
{
pthread_join(tid_produce[i], NULL);
printf("count[%d] = %d\n", i, count[i]); //输出每个生产者线程工作次数
total_produce += count[i];
}
printf("total_produce = %d\n", total_produce);
int total_consume = 0;
for (i = 0; i < MAXCONSUME; i++)
pthread_join(tid_consume[i], NULL);
total_consume += ready.eat;
printf("total_consume = %d\n", total_consume);
return 0;
}
void *produce(void *arg)
{
for (;;)
{
pthread_mutex_lock(&task.mutex);
if (task.nput >= MAXITEMS) //生产任务已完成
{
pthread_mutex_unlock(&task.mutex);
return NULL;
}
else
{ //生产线程进行生产工作
task.buff[task.nput] = task.nval; //资源按顺序赋值
task.nput++;
task.nval++;
pthread_mutex_unlock(&task.mutex);
(*(int *)arg)++; //对已生产任务进行计数
int zero = 0;
pthread_mutex_lock(&ready.mutex);
if (ready.n == 0)
zero = 1;
ready.n++; //消费者可消费数目
pthread_mutex_unlock(&ready.mutex);
if (zero)
pthread_cond_signal(&ready.cond); //只在资源由0变为1时才通知消费者
}
}
}
void *consume(void *arg)
{
int i;
for (;;)
{
pthread_mutex_lock(&task.mutex);
pthread_mutex_lock(&ready.mutex);
if (task.nput >= MAXPRODUCE && ready.n == 0) //生产者生产完毕且可消费资源数为0时,退出
{
pthread_mutex_unlock(&task.mutex);
pthread_mutex_unlock(&ready.mutex);
return NULL;
}
pthread_mutex_unlock(&task.mutex);
while (ready.n == 0) //可消费数为0
pthread_cond_wait(&ready.cond, &ready.mutex); //等待生产者生产,并通知
ready.n--; //消费者消费资源
ready.eat++; //增加已消费数目
pthread_mutex_unlock(&ready.mutex);
}
}
4.分析代码
其实把问题和生产消费过程想清楚也没什么好分析的,需要注意的一点是main函数内的pthread_setconcurrency(MAXPRODUCE + MAXCONSUME);
这函数比较少见,但很重要,该函数是设置并发线程数的。为什么要设置呢,难道不是创建的所有线程都会执行吗?还真不是,有些系统可能你创建了很多的线程,但实际情况是就第一个线程在执行,其他线程都在打酱油。
实验结果
...中间省略若干行