1. 程式人生 > >秒殺多執行緒第十一篇 讀者寫者問題

秒殺多執行緒第十一篇 讀者寫者問題

               

與上一篇《秒殺多執行緒第十篇 生產者消費者問題》的生產者消費者問題一樣,讀者寫者也是一個非常著名的同步問題。讀者寫者問題描述非常簡單,有一個寫者很多讀者,多個讀者可以同時讀檔案,但寫者在寫檔案時不允許有讀者在讀檔案,同樣有讀者在讀檔案時寫者也不去能寫檔案。

上面是讀者寫者問題示意圖,類似於生產者消費者問題的分析過程,首先來找找哪些是屬於“等待”情況。

第一.寫者要等到沒有讀者時才能去寫檔案。

第二.所有讀者要等待寫者完成寫檔案後才能去讀檔案。

找完“等待”情況後,再看看有沒有要互斥訪問的資源。由於只有一個寫者而讀者們是可以共享的讀檔案,所以按題目要求並沒有需要互斥訪問的資源。類似於上一篇中美觀的彩色輸出,我們對生產者輸出程式碼進行了顏色設定(在控制檯輸出顏色設定參見《

VC 控制檯顏色設定》)。因此在這裡要加個互斥訪問,不然很有可能在寫者執行緒將控制檯顏色設定還原之前,讀者執行緒就已經有輸出了。所以要對輸出語句作個互斥訪問處理,修改後的讀者及寫者的輸出函式如下所示:

//讀者執行緒輸出函式void ReaderPrintf(char *pszFormat, ...){ va_list   pArgList; va_start(pArgList, pszFormat); EnterCriticalSection(&g_cs); vfprintf(stdout, pszFormat, pArgList); LeaveCriticalSection(&g_cs); va_end(pArgList);}//寫者執行緒輸出函式
void WriterPrintf(char *pszStr){ EnterCriticalSection(&g_cs); SetConsoleColor(FOREGROUND_GREEN); printf("     %s\n", pszStr); SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); LeaveCriticalSection(&g_cs);}

讀者執行緒輸出函式所使用的可變引數詳見《》。

解決了互斥輸出問題,接下來再考慮如何實現同步問題。可以設定一個變數來記錄正在讀檔案的讀者個數,第一個開始讀檔案的讀者要負責將關閉允許寫者進入的標誌,最後一個結束讀檔案的讀者要負責開啟允許寫者進入的標誌。這樣第一種“等待”情況就解決了。第二種“等待”情況是有寫者進入時所以讀者不能進入,使用一個事件就可以完成這個任務了——所有讀者都要等待這個事件而寫者負責觸發事件和設定事件為未觸發。詳細見程式碼中註釋:

//讀者與寫者問題#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 READER_NUM = 5//讀者個數//關鍵段和事件CRITICAL_SECTION g_cs, g_cs_writer_count;HANDLE g_hEventWriter, g_hEventNoReader;int g_nReaderCount;//讀者執行緒輸出函式(變參函式的實現)void ReaderPrintf(char *pszFormat, ...){ va_list   pArgList;  va_start(pArgList, pszFormat); EnterCriticalSection(&g_cs); vfprintf(stdout, pszFormat, pArgList); LeaveCriticalSection(&g_cs); va_end(pArgList);}//讀者執行緒函式unsigned int __stdcall ReaderThreadFun(PVOID pM){ ReaderPrintf("     編號為%d的讀者進入等待中...\n", GetCurrentThreadId()); //等待寫者完成 WaitForSingleObject(g_hEventWriter, INFINITE); //讀者個數增加 EnterCriticalSection(&g_cs_writer_count); g_nReaderCount++; if (g_nReaderCount == 1)  ResetEvent(g_hEventNoReader); LeaveCriticalSection(&g_cs_writer_count); //讀取檔案 ReaderPrintf("編號為%d的讀者開始讀取檔案...\n", GetCurrentThreadId()); Sleep(rand() % 100); //結束閱讀,讀者個數減小,空位增加 ReaderPrintf(" 編號為%d的讀者結束讀取檔案\n", GetCurrentThreadId()); //讀者個數減少 EnterCriticalSection(&g_cs_writer_count); g_nReaderCount--; if (g_nReaderCount == 0)  SetEvent(g_hEventNoReader); LeaveCriticalSection(&g_cs_writer_count); return 0;}//寫者執行緒輸出函式void WriterPrintf(char *pszStr){ EnterCriticalSection(&g_cs); SetConsoleColor(FOREGROUND_GREEN); printf("     %s\n", pszStr); SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); LeaveCriticalSection(&g_cs);}//寫者執行緒函式unsigned int __stdcall WriterThreadFun(PVOID pM){ WriterPrintf("寫者執行緒進入等待中..."); //等待讀檔案的讀者為零 WaitForSingleObject(g_hEventNoReader, INFINITE); //標記寫者正在寫檔案 ResetEvent(g_hEventWriter);   //寫檔案 WriterPrintf("  寫者開始寫檔案....."); Sleep(rand() % 100); WriterPrintf("  寫者結束寫檔案"); //標記寫者結束寫檔案 SetEvent(g_hEventWriter); return 0;}int main()printf("  讀者寫者問題\n"); printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n"); //初始化事件和訊號量 InitializeCriticalSection(&g_cs); InitializeCriticalSection(&g_cs_writer_count); //手動置位,初始已觸發 g_hEventWriter = CreateEvent(NULL, TRUE, TRUE, NULL); g_hEventNoReader  = CreateEvent(NULL, FALSE, TRUE, NULL); g_nReaderCount = 0int i; HANDLE hThread[READER_NUM + 1]; //先啟動二個讀者執行緒 for (i = 1; i <= 2; i++)  hThread[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThreadFun, NULL, 0, NULL); //啟動寫者執行緒 hThread[0] = (HANDLE)_beginthreadex(NULL, 0, WriterThreadFun, NULL, 0, NULL); Sleep(50); //最後啟動其它讀者結程 for ( ; i <= READER_NUM; i++)  hThread[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThreadFun, NULL, 0, NULL); WaitForMultipleObjects(READER_NUM + 1, hThread, TRUE, INFINITE); for (i = 0; i < READER_NUM + 1; i++)  CloseHandle(hThread[i]); //銷燬事件和訊號量 CloseHandle(g_hEventWriter); CloseHandle(g_hEventNoReader); DeleteCriticalSection(&g_cs); DeleteCriticalSection(&g_cs_writer_count); return 0;}

執行結果如下所示:

根據結果可以看出當有讀者在讀檔案時,寫者執行緒會進入等待狀態中。當寫者執行緒在寫檔案時,讀者執行緒也會排隊等待,說明讀者和寫者已經完成了同步。

本系列通過來列舉執行緒同步手段的關鍵段事件互斥量訊號量,並作對這四種方法進行了總結。然後又通過二個著名的執行緒同步例項——讀者寫者問題來強化對多執行緒同步互斥的理解與運用。希望讀者們能夠熟練掌握,從而在筆試面試中能夠順利的“秒殺”多執行緒的相關試題,獲得自己滿意的offer

從《生產者消費者問題》到《讀者寫者問題》可以得出多執行緒問題的關鍵在於找到所有“等待”情況和判斷有無需要互斥訪問的資源。那麼如何從實際問題中更好更快更全面的找出這些了?請看《秒殺多執行緒第十二篇多執行緒同步內功心法——PV操作上》和《秒殺多執行緒第十三篇多執行緒同步內功心法——PV操作下》這二篇以加強解決多執行緒同步問題的“內功”。