C語言實戰——生產者消費者問題

C語言實戰——生產者消費者問題

方法摘要

生產者消費者共享緩衝區,生產者向緩衝區中放數據,消費者從緩衝取中取數據,當緩衝區中被放滿時,生產者進程就必須進入掛起狀態,直到消費者從緩衝中取走數據時,生產者才能繼續向緩衝區中存放數據,同樣當緩衝取中沒有數據時,消費者進程就必須進入掛起休眠狀態,直到生產者向緩衝區中放入數據時,消費者才能被喚醒繼續從緩衝區中取走數據。

簡寫代碼

  • 對於生產者:
void producer(void)
{
	int item;
	while(1)
	{
		item = produce_item();
		if(count == N)					//如果緩衝區滿就休眠
		sleep();
		insert_item(item);
		count = count + 1;				//緩衝區數據項計數加1
		if(count == 1)
		wakeup(consumer);
	}
}
  • 這裏的函數名只是簡寫,並沒有按照要求,只是把其功能簡單表述。
  • 對於消費者:
void consumer(void)
{
	int item;
	while(TRUE)
	{
		if(count == 0)				//如果緩衝區空就休眠
			sleep();
		item = remove_item();
		count = count - 1;			//緩衝區數據項計數減1
		if(count == N - 1)
			wakeup(producer);
		consume_item(item);
	}
}
  • 然而在特殊的情況下,即進程切換時,生產者喚醒消費者的新號可能會丟失,這樣會導致生產者和消費者遲早都會休眠,導致死鎖。

信號量的引入

  • down和up(分別爲一般化後的sleep和wakeup)。對一個信號量執行down操作,則是檢查其值是否大於0。若該值大於0,則將其減1(即用掉一個保存的喚醒信號)並繼續;若該值爲0,則進程將睡眠,而且此時down操作並未結束。檢查數值、修改變量值以及可能發生的睡眠操作均作爲一個單一的、不可分割的原子操作完成。保證一旦一個信號量操作開始,則在該操作完成或阻塞之前,其他進程均不允許訪問該信號量。這種原子性對於解決同步問題和避免競爭條件是絕對必要的。所謂原子操作,是指一組相關聯的操作要麼都不間斷地執行,要麼不執行。
  • up操作對信號量的值增1。如果一個或多個進程在該信號量上睡眠,無法完成一個先前的down操作,則由系統選擇其中的一個(如隨機挑選)並允許該進程完成它的down操作。於是,對一個有進程在其上睡眠的信號量執行一次up操作後,該信號量的值仍舊是0,但在其上睡眠的進程卻少了一個。信號量的值增加1和喚醒一個進程同樣也是不可分割的,不會有某個進程因執行up而阻塞,正如前面的模型中不會有進程因執行wakeup而阻塞一樣。
  • P,V操作的含義:P(S)表示申請一個資源,S.value>0表示有資源可用,其值爲資源的數目;S.value=0表示無資源可用;S.value<0則他的大小表示S等待隊列中的進程個數。V(S)表示釋放一個資源,信號量的初值應大於等於0。P操作相當於“等待一個信號”,V操作相當於“發送一個新號”,在實現互斥過程中,V操作相當於發送一個信號說臨界資源可用了。實際上,在實現互斥時,P、V操作相當於申請資源和釋放資源。
  • 該解決方案使用了三個信號量:一個稱爲full,用來記錄充滿緩衝槽數目,一個稱爲empty,記錄空的緩衝槽總數;一個稱爲mutex,用來確保生產者和消費者不會同時訪問緩衝區。full的初值爲0,empty的初值爲緩衝區中槽的個數,mutex的初值是1。供兩個或多個進程使用的信號量,其初值爲1,保證同時只有一個進程可以進入臨界區,稱爲二元信號量。如果每個進程在進入臨界區前都執行了down操作,並在退出時執行一個up操作,就能夠實現互斥。
#define N 100
typedef int semaphore;
semaphore mutex = 1;
semaphore empty = N;
semaphore full = 0;
void producer(void)
{
	int item;
	while(TRUE)
	{
		item = produce_item();
		down(&empty);				//空槽數目減1,相當於P(empty)
		down(&mutex);				//進入臨界區,相當於P(mutex)
		insert_item(item);			//將新數據放到緩衝區中
		up(&mutex);				//離開臨界區,相當於V(mutex)
		up(&full);				//滿槽數目加1,相當於V(full)
	}
}
void consumer(void)
{
	int item;
	while(TRUE)
	{
		down(&full);				//將滿槽數目減1,相當於P(full)
		down(&mutex);				//進入臨界區,相當於P(mutex)
		item = remove_item();	   		 //從緩衝區中取出數據
		up(&mutex);				//離開臨界區,相當於V(mutex)		
		up(&empty);				//將空槽數目加1 ,相當於V(empty)
		consume_item(item);			//處理取出的數據項
	}
}
  • 信號量的另一種用途是用於實現同步,信號量full和empty用來保證某種事件的順序發生或不發生。在本例中,它們保證當緩衝區滿的時候生產者停止運行,以及當緩衝區空的時候消費者停止運行。

代碼實現

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define N 100
#define true 1
#define producerNum  5
#define consumerNum  5
#define sleepTime 5

typedef int semaphore;
typedef int item;
item buffer[N] = {0};
int in = 0;
int out = 0;
int proCount = 0;
semaphore mutex = 1, empty = N, full = 0, proCmutex = 1;

void * producer(void * a)
{
    while(1)
	{
        while(proCmutex <= 0);
        proCmutex--;
        proCount++;
        printf("生產一個產品ID%d,緩衝區位置爲%d\n",proCount,in);
        proCmutex++;

        while(empty <= 0)
		{
            printf("緩衝區已滿!\n");
        }
        empty--;

        while(mutex <= 0);
        mutex--;

        buffer[in] = proCount;
        in = (in + 1) % N;

        mutex++;
        full++;
        sleep(sleepTime);
    }
}

void * consumer(void *b)
{
    while(1)
	{
        while(full <= 0)
		{
            printf("緩衝區爲空!\n");
        }
        full--;

        while(mutex <= 0);
        mutex--;

        int nextc = buffer[out];
        buffer[out] = 0;//消費完將緩衝區設置爲0

        out = (out + 1) % N;

        mutex++;
        empty++;

        printf(" 消費一個產品ID%d,緩衝區位置爲%d\n", nextc,out);
        sleep(sleepTime);
    }
}

int main()
{
    pthread_t threadPool[producerNum+consumerNum];
    int i;
    for(i = 0; i < producerNum; i++){
        pthread_t temp;
        if(pthread_create(&temp, NULL, producer, NULL) == -1){
            printf("ERROR, fail to create producer%d\n", i);
            exit(1);
        }
        threadPool[i] = temp;
    }//創建生產者進程放入線程池


    for(i = 0; i < consumerNum; i++){
        pthread_t temp;
        if(pthread_create(&temp, NULL, consumer, NULL) == -1){
            printf("ERROR, fail to create consumer%d\n", i);
            exit(1);
        }
        threadPool[i+producerNum] = temp;
    }//創建消費者進程放入線程池


    void * result;
    for(i = 0; i < producerNum+consumerNum; i++){
        if(pthread_join(threadPool[i], &result) == -1){
            printf("fail to recollect\n");
            exit(1);
        }
    }//運行線程池
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章