多線程面試題系列(14):讀者寫者問題繼 讀寫鎖SRWLock
在第十一篇文章中我們使用事件和一個記錄讀者個數的變量來解決讀者寫者問題。問題雖然得到了解決,但代碼有點復雜。本篇將介紹一種新方法——讀寫鎖SRWLock來解決這一問題。讀寫鎖在對資源進行保護的同時,還能區分想要讀取資源值的線程(讀取者線程)和想要更新資源的線程(寫入者線程)。對於讀取者線程,讀寫鎖會允許他們並發的執行。當有寫入者線程在占有資源時,讀寫鎖會讓其它寫入者線程和讀取者線程等待。因此用讀寫鎖來解決讀者寫者問題會使代碼非常清晰和簡潔。
下面就來看看如何使用讀寫鎖,要註意編譯讀寫鎖程序需要VS2008,運行讀寫鎖程序要在Vista或Windows Server2008系統(比這兩個更高級的系統也可以)。讀寫鎖的主要函數就五個,分為初始化函數,寫入者線程申請和釋放函數,讀取者線程申請和釋放函數,以下是詳細的函數使用說明:
第一個 InitializeSRWLock
函數功能:初始化讀寫鎖
函數原型:VOID InitializeSRWLock(PSRWLOCK SRWLock);
函數說明:初始化(沒有刪除或銷毀SRWLOCK的函數,系統會自動清理)
第二個 AcquireSRWLockExclusive
函數功能:寫入者線程申請寫資源。
函數原型:VOID AcquireSRWLockExclusive(PSRWLOCK SRWLock);
第三個 ReleaseSRWLockExclusive
函數功能:寫入者線程寫資源完畢,釋放對資源的占用。
函數原型:VOID ReleaseSRWLockExclusive(PSRWLOCK SRWLock);
第四個 AcquireSRWLockShared
函數功能:讀取者線程申請讀資源。
函數原型:VOID AcquireSRWLockShared(PSRWLOCK SRWLock);
第五個 ReleaseSRWLockShared
函數功能:讀取者線程結束讀取資源,釋放對資源的占用。
函數原型:VOID ReleaseSRWLockShared(PSRWLOCK SRWLock);
註意一個線程僅能鎖定資源一次,不能多次鎖定資源。
使用讀寫鎖精簡後的代碼如下(代碼中變參函數的實現請參閱《C,C++中使用可變參數》,控制臺顏色設置請參閱《VC 控制臺顏色設置》):
[cpp]- //讀者與寫者問題繼 讀寫鎖SRWLock
- #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;
- SRWLOCK g_srwLock;
- //讀者線程輸出函數(變參函數的實現)
- 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());
- //讀者申請讀取文件
- AcquireSRWLockShared(&g_srwLock);
- //讀取文件
- ReaderPrintf("編號為%d的讀者開始讀取文件...\n", GetCurrentThreadId());
- Sleep(rand() % 100);
- ReaderPrintf(" 編號為%d的讀者結束讀取文件\n", GetCurrentThreadId());
- //讀者結束讀取文件
- ReleaseSRWLockShared(&g_srwLock);
- 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("寫者線程進入等待中...");
- //寫者申請寫文件
- AcquireSRWLockExclusive(&g_srwLock);
- //寫文件
- WriterPrintf(" 寫者開始寫文件.....");
- Sleep(rand() % 100);
- WriterPrintf(" 寫者結束寫文件");
- //標記寫者結束寫文件
- ReleaseSRWLockExclusive(&g_srwLock);
- return 0;
- }
- int main()
- {
- printf(" 讀者寫者問題繼 讀寫鎖SRWLock\n");
- printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
- //初始化讀寫鎖和關鍵段
- InitializeCriticalSection(&g_cs);
- InitializeSRWLock(&g_srwLock);
- HANDLE hThread[READER_NUM + 1];
- int i;
- //先啟動二個讀者線程
- 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]);
- //銷毀關鍵段
- DeleteCriticalSection(&g_cs);
- return 0;
- }
對比下第十一篇中的代碼就可以發現這份代碼確實清爽許多了。這個程序用VS2008編譯可以通過,但在XP系統下運行會導致報錯。
在Win7系統下能夠正確的運行,結果如圖所示:
最後總結一下讀寫鎖SRWLock
1.讀寫鎖聲明後要初始化,但不用銷毀,系統會自動清理讀寫鎖。
2.讀取者和寫入者分別調用不同的申請函數和釋放函數。
多線程面試題系列(14):讀者寫者問題繼 讀寫鎖SRWLock