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