繼經典線程同步問題之後,我們來看看生產者消費者問題及讀者寫者問題。生產者消費者問題是一個著名的線程同步問題,該問題描述如下:有一個生產者在生產產品,這些產品將提供給若干個消費者去消費,為了使生產者和消費者能並發執行,在兩者之間設定一個具有多個緩衝區的緩衝池,生產者將它生產的產品放入一個緩衝區中,消費者可以從緩衝區中取走產品進行消費,顯然生產者和消費者之間必須保持同步,即不允許消費者到一個空的緩衝區中取產品,也不允許生產者向一個已經放入產品的緩衝區中再次投放產品。
這個生產者消費者題目不僅常用於作業系統的課程設計,也常常在程式員和軟體設計師考試中出現。並且在電腦考研的專業課考試中也是一個非常熱門的問題。因此現在就針對這個問題進行詳細深入的解答。
首先來簡化問題,先假設生產者和消費者都只有一個,且緩衝區也只有一個。這樣情況就簡便多了。
第一.從緩衝區取出產品和向緩衝區投放產品必須是互斥進行的。可以用關鍵段和互斥量來完成。
第二.生產者要等待緩衝區為空白,這樣才可以投放產品,消費者要等待緩衝區不為空白,這樣才可以取出產品進行消費。並且由於有二個等待過程,所以要用二個事件或訊號量來控制。
考慮這二點後,代碼很容易寫出來。另外為了美觀起見,將消費者的輸出顏色設定為彩色,有關如何在控制台下設定彩色輸出請參閱《VC 控制台顏色設定》。
//1生產者 1消費者 1緩衝區//使用二個事件,一個表示緩衝區空,一個表示緩衝區滿。//再使用一個關鍵段來控制緩衝區的訪問#include <stdio.h>#include <process.h>#include <windows.h>//設定控制台輸出顏色BOOL SetConsoleColor(WORD wAttributes){HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);if (hConsole == INVALID_HANDLE_VALUE)return FALSE;return SetConsoleTextAttribute(hConsole, wAttributes);}const int END_PRODUCE_NUMBER = 10; //生產產品個數int g_Buffer; //緩衝區//事件與關鍵段CRITICAL_SECTION g_cs;HANDLE g_hEventBufferEmpty, g_hEventBufferFull;//生產者線程函數unsigned int __stdcall ProducerThreadFun(PVOID pM){for (int i = 1; i <= END_PRODUCE_NUMBER; i++){//等待緩衝區為空白WaitForSingleObject(g_hEventBufferEmpty, INFINITE);//互斥的訪問緩衝區EnterCriticalSection(&g_cs);g_Buffer = i;printf("生產者將資料%d放入緩衝區\n", i);LeaveCriticalSection(&g_cs);//通知緩衝區有新資料了SetEvent(g_hEventBufferFull);}return 0;}//消費者線程函數unsigned int __stdcall ConsumerThreadFun(PVOID pM){volatile bool flag = true;while (flag){//等待緩衝區中有資料WaitForSingleObject(g_hEventBufferFull, INFINITE);//互斥的訪問緩衝區EnterCriticalSection(&g_cs);SetConsoleColor(FOREGROUND_GREEN);printf(" 消費者從緩衝區中取資料%d\n", g_Buffer);SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);if (g_Buffer == END_PRODUCE_NUMBER)flag = false;LeaveCriticalSection(&g_cs);//通知緩衝區已為空白SetEvent(g_hEventBufferEmpty);Sleep(10); //some other work should to do}return 0;}int main(){printf(" 生產者消費者問題 1生產者 1消費者 1緩衝區\n");printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");InitializeCriticalSection(&g_cs);//建立二個自動複位事件,一個表示緩衝區是否為空白,另一個表示緩衝區是否已經處理g_hEventBufferEmpty = CreateEvent(NULL, FALSE, TRUE, NULL);g_hEventBufferFull = CreateEvent(NULL, FALSE, FALSE, NULL);const int THREADNUM = 2;HANDLE hThread[THREADNUM];hThread[0] = (HANDLE)_beginthreadex(NULL, 0, ProducerThreadFun, NULL, 0, NULL);hThread[1] = (HANDLE)_beginthreadex(NULL, 0, ConsumerThreadFun, NULL, 0, NULL);WaitForMultipleObjects(THREADNUM, hThread, TRUE, INFINITE);CloseHandle(hThread[0]);CloseHandle(hThread[1]);//銷毀事件和關鍵段CloseHandle(g_hEventBufferEmpty);CloseHandle(g_hEventBufferFull);DeleteCriticalSection(&g_cs);return 0;}
運行結果如下所示:
可以看出生產者與消費者已經是有序的工作了。
然後再對這個簡單生產者消費者問題加大難度。將消費者改成2個,緩衝池改成擁有4個緩衝區的大緩衝池。
如何來思考了這個問題了?首先根據上面分析的二點,可以知道生產者和消費者由一個變成多個的影響不大,唯一要注意的是緩衝池變大了,回顧一下《秒殺多線程第八篇 經典線程同步 訊號量Semaphore》中的訊號量,不難得出用二個訊號量就可以解決這種緩衝池有多個緩衝區的情況——用一個訊號量A來記錄為空白的緩衝區個數,另一個訊號量B記錄非空的緩衝區個數,然後生產者等待訊號量A,消費者等待訊號量B就可以了。因此可以仿照上面的代碼來實現複雜生產者消費者問題,範例程式碼如下:
//1生產者 2消費者 4緩衝區#include <stdio.h>#include <process.h>#include <windows.h>//設定控制台輸出顏色BOOL SetConsoleColor(WORD wAttributes){HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);if (hConsole == INVALID_HANDLE_VALUE)return FALSE;return SetConsoleTextAttribute(hConsole, wAttributes);}const int END_PRODUCE_NUMBER = 8; //生產產品個數const int BUFFER_SIZE = 4; //緩衝區個數int g_Buffer[BUFFER_SIZE]; //緩衝池int g_i, g_j;//訊號量與關鍵段CRITICAL_SECTION g_cs;HANDLE g_hSemaphoreBufferEmpty, g_hSemaphoreBufferFull;//生產者線程函數unsigned int __stdcall ProducerThreadFun(PVOID pM){for (int i = 1; i <= END_PRODUCE_NUMBER; i++){//等待有空的緩衝區出現WaitForSingleObject(g_hSemaphoreBufferEmpty, INFINITE);//互斥的訪問緩衝區EnterCriticalSection(&g_cs);g_Buffer[g_i] = i;printf("生產者在緩衝池第%d個緩衝區中投放資料%d\n", g_i, g_Buffer[g_i]);g_i = (g_i + 1) % BUFFER_SIZE;LeaveCriticalSection(&g_cs);//通知消費者有新資料了ReleaseSemaphore(g_hSemaphoreBufferFull, 1, NULL);}printf("生產者完成任務,線程結束運行\n");return 0;}//消費者線程函數unsigned int __stdcall ConsumerThreadFun(PVOID pM){while (true){//等待非空的緩衝區出現WaitForSingleObject(g_hSemaphoreBufferFull, INFINITE);//互斥的訪問緩衝區EnterCriticalSection(&g_cs);SetConsoleColor(FOREGROUND_GREEN);printf(" 編號為%d的消費者從緩衝池中第%d個緩衝區取出資料%d\n", GetCurrentThreadId(), g_j, g_Buffer[g_j]);SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);if (g_Buffer[g_j] == END_PRODUCE_NUMBER)//結束標誌{LeaveCriticalSection(&g_cs);//通知其它消費者有新資料了(結束標誌)ReleaseSemaphore(g_hSemaphoreBufferFull, 1, NULL);break;}g_j = (g_j + 1) % BUFFER_SIZE;LeaveCriticalSection(&g_cs);Sleep(50); //some other work to doReleaseSemaphore(g_hSemaphoreBufferEmpty, 1, NULL);}SetConsoleColor(FOREGROUND_GREEN);printf(" 編號為%d的消費者收到通知,線程結束運行\n", GetCurrentThreadId());SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);return 0;}int main(){printf(" 生產者消費者問題 1生產者 2消費者 4緩衝區\n");printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");InitializeCriticalSection(&g_cs);//初始化訊號量,一個記錄有產品的緩衝區個數,另一個記錄空緩衝區個數.g_hSemaphoreBufferEmpty = CreateSemaphore(NULL, 4, 4, NULL);g_hSemaphoreBufferFull = CreateSemaphore(NULL, 0, 4, NULL);g_i = 0;g_j = 0;memset(g_Buffer, 0, sizeof(g_Buffer));const int THREADNUM = 3;HANDLE hThread[THREADNUM];//生產者線程hThread[0] = (HANDLE)_beginthreadex(NULL, 0, ProducerThreadFun, NULL, 0, NULL);//消費者線程hThread[1] = (HANDLE)_beginthreadex(NULL, 0, ConsumerThreadFun, NULL, 0, NULL);hThread[2] = (HANDLE)_beginthreadex(NULL, 0, ConsumerThreadFun, NULL, 0, NULL);WaitForMultipleObjects(THREADNUM, hThread, TRUE, INFINITE);for (int i = 0; i < THREADNUM; i++)CloseHandle(hThread[i]);//銷毀訊號量和關鍵段CloseHandle(g_hSemaphoreBufferEmpty);CloseHandle(g_hSemaphoreBufferFull);DeleteCriticalSection(&g_cs);return 0;}
運行結果如所示:
輸出結果證明各線程的同步和互斥已經完成了。
至此,生產者消費者問題已經圓滿的解決了,下面作個總結:
1.首先要考慮生產者與消費者對緩衝區操作時的互斥。
2.不管生產者與消費者有多少個,緩衝池有多少個緩衝區。都只有二個同步過程——分別是生產者要等待有空緩衝區才能投放產品,消費者要等待有非空緩衝區才能去取產品。
下一篇《秒殺多線程第十一篇讀者寫者問題》將介紹另一個著名的同步問題——讀者寫者問題,歡迎大家再來參閱。
轉載請標明出處,原文地址:http://blog.csdn.net/morewindows/article/details/7577591
如果覺得本文對您有協助,請點擊‘頂’支援一下,您的支援是我寫作最大的動力,謝謝。