生產者-消費者問題是進程間或線程間同步的經典問題。該問題有多種實現,如消息隊列、共享內存等,這次我們用的是多線程和互斥鎖以及條件變量來實現該問題。
動手寫程序之前先得把問題想明白。
1.該問題難在什麼地方?
難點其實只有一個,就是同步問題。這個同步涉及了以下幾個方面:多個生產者之間的同步(如:生產資源的總數的同步,生產的資源不會亂序的同步)、多個消費者之間的同步(如:消費資源的總數的同步,現有資源數目的同步)、生產者與消費者之間的同步(如:若現有資源爲0,消費者要等生產者生產後才能繼續消費)
2.如何解決上述的同步問題?
解決方法就是使用互斥鎖和條件變量。如何使用呢?
1.可以將生產的資源相關信息和互斥鎖A捆綁在一起,這樣生產者生產之前先獲取互斥鎖A,再進行生產,更新資源信息。這便解決了生產者間的同步問題。
2.可以將消費資源相關信息和互斥鎖B捆綁在一起,這樣消費者消費之前先獲取互斥鎖B,再進行消費,更新消費信息。這便解決了消費者間的同步問題。
3.生產者和互斥鎖直接的同步呢?這就是條件變量發揮作用的地方。消費者消費時,若發現現有資源數爲0,則使用條件變量等待生產者生產資源,生產者生產資源後,便可通過條件變量通知消費者繼續消費。
3.代碼
#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);
這函數比較少見,但很重要,該函數是設置併發線程數的。爲什麼要設置呢,難道不是創建的所有線程都會執行嗎?還真不是,有些系統可能你創建了很多的線程,但實際情況是就第一個線程在執行,其他線程都在打醬油。
實驗結果
...中間省略若干行