生產者-消費者問題

生產者-消費者問題是進程間或線程間同步的經典問題。該問題有多種實現,如消息隊列、共享內存等,這次我們用的是多線程和互斥鎖以及條件變量來實現該問題。


動手寫程序之前先得把問題想明白。


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);

這函數比較少見,但很重要,該函數是設置併發線程數的。爲什麼要設置呢,難道不是創建的所有線程都會執行嗎?還真不是,有些系統可能你創建了很多的線程,但實際情況是就第一個線程在執行,其他線程都在打醬油。


實驗結果


...中間省略若干行





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章