秒殺多線程第十篇 生產者消費者問題

來源:互聯網
上載者:User

    繼經典線程同步問題之後,我們來看看生產者消費者問題及讀者寫者問題。生產者消費者問題是一個著名的線程同步問題,該問題描述如下:有一個生產者在生產產品,這些產品將提供給若干個消費者去消費,為了使生產者和消費者能並發執行,在兩者之間設定一個具有多個緩衝區的緩衝池,生產者將它生產的產品放入一個緩衝區中,消費者可以從緩衝區中取走產品進行消費,顯然生產者和消費者之間必須保持同步,即不允許消費者到一個空的緩衝區中取產品,也不允許生產者向一個已經放入產品的緩衝區中再次投放產品。

    這個生產者消費者題目不僅常用於作業系統的課程設計,也常常在程式員和軟體設計師考試中出現。並且在電腦考研的專業課考試中也是一個非常熱門的問題。因此現在就針對這個問題進行詳細深入的解答。

 

    首先來簡化問題,先假設生產者和消費者都只有一個,且緩衝區也只有一個。這樣情況就簡便多了。

    第一.從緩衝區取出產品和向緩衝區投放產品必須是互斥進行的。可以用關鍵段和互斥量來完成。

    第二.生產者要等待緩衝區為空白,這樣才可以投放產品,消費者要等待緩衝區不為空白,這樣才可以取出產品進行消費。並且由於有二個等待過程,所以要用二個事件或訊號量來控制。

    考慮這二點後,代碼很容易寫出來。另外為了美觀起見,將消費者的輸出顏色設定為彩色,有關如何在控制台下設定彩色輸出請參閱《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

如果覺得本文對您有協助,請點擊‘頂’支援一下,您的支援是我寫作最大的動力,謝謝。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.