8.5 Slim讀/寫鎖(SRWLock)——輕量級的讀寫鎖windows
(1)SRWLock鎖的目的數組
①容許讀者線程同一時刻訪問共享資源(由於不存在破壞數據的風險)函數
②寫者線程應獨佔資源的訪問權,任何其餘線程(含寫入的線程)要等這個寫者線程訪問完才能得到資源。性能
(2)SRWlock鎖的使用方法測試
①初始化SRWLOCK結構體 InitializeSRWLock(PSRWLOCK pSRWLock);ui
②寫者線程調用AcquireSRWLockExclusive(pSRWLock);以排它方式訪問spa
讀者線程調用AcquireSRWLockShared以共享方式訪問線程
③訪問完畢後,寫者線程調用ReleaseSRWLockExclusive解鎖。讀者線程要調用ReleaseSRWLockShared解鎖指針
④注意SRWLock不須要刪除和銷燬,因此不用Delete之類的,系統會自動清理。code
(3)SRWLock鎖的共享規則:
①若當前鎖的狀態是「寫」(即某個線程已經得到排它鎖),這時其餘線程,無論是申請讀或寫鎖的線程,都會被阻塞在AcquireSRWLock*函數中。讀鎖或寫鎖等待計數加1。
②若當前鎖的狀態是「讀」(即某個(些)線程已經得到了共享鎖)。
A、若是新的線程申請寫鎖,則此時它將被掛起,鎖的寫等待計數加1。直至當前正在讀鎖的線程所有結束,而後系統會喚醒正在等待寫的線程,即申請排他鎖要在沒有任何其餘鎖的時候才能返回。
B、若是新的線程申請讀鎖,若此時沒有寫線程正在等待,則容許讀鎖進入而不會被阻塞。若是有寫鎖正在等待,則寫鎖優先獲得鎖,新線程進入等待,讀鎖計數加1(這樣作的目的是讓寫鎖有機會進入)。
(4)SRWLock與臨界區的不一樣
①不存在TryEnter(Shared/Exclusive)SRWLock之類的函數;若是鎖己經被佔用,那麼調用AcquireSRWLock(Shared/Exclusive)會阻塞調用線程。(如排它寫入時,鎖會被獨佔)(課本這說法已不適用了,在Win7之後系統提供了TryAcquireSRWLock(Exclusive/Shared)等函數,能夠實現這需求了)
②不能遞歸得到SRWLock,即一個線程不能爲了屢次寫入資源而屢次鎖定資源,再屢次調用ReleaseSRWLock*來釋放鎖。
【SRWLock程序】
#include <windows.h> #include <tchar.h> #include <locale.h> #include <time.h> ////////////////////////////////////////////////////////////////////////// const int g_iThreadCnt = 20; int g_iGlobalValue = 0; ////////////////////////////////////////////////////////////////////////// SRWLOCK g_sl = { 0 }; ////////////////////////////////////////////////////////////////////////// DWORD WINAPI ReadThread(PVOID pParam); DWORD WINAPI WriteThread(PVOID pParam); ////////////////////////////////////////////////////////////////////////// int _tmain() { _tsetlocale(LC_ALL, _T("chs")); srand((unsigned int)time(NULL)); //讀寫鎖只需初始化,不須要手動釋放,系統會自行處理 InitializeSRWLock(&g_sl); HANDLE aThread[g_iThreadCnt]; DWORD dwThreadId = 0; SYSTEM_INFO si = { 0 }; GetSystemInfo(&si); for (int i = 0; i < g_iThreadCnt;i++){ if (0 == rand()%2) aThread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)WriteThread, NULL, CREATE_SUSPENDED,&dwThreadId); else aThread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ReadThread, NULL, CREATE_SUSPENDED, &dwThreadId); SetThreadAffinityMask(aThread[i],1<<(i % si.dwNumberOfProcessors)); ResumeThread(aThread[i]); } //等待全部線程結束 WaitForMultipleObjects(g_iThreadCnt,aThread,TRUE,INFINITE); for (int i = 0; i < g_iThreadCnt;i++){ CloseHandle(aThread[i]); } _tsystem(_T("PAUSE")); return 0; } //////////////////////////////////////////////////////////////////////////// ////不加保護的讀寫 //DWORD WINAPI ReadThread(PVOID pParam) //{ // _tprintf(_T("Timestamp[%u]:Thread[ID:0x%x]讀取全局變量值爲%d\n"), // GetTickCount(),GetCurrentThreadId(),g_iGlobalValue); // return 0; //} // //////////////////////////////////////////////////////////////////////////// //DWORD WINAPI WriteThread(PVOID pParam) //{ // // for (int i = 0; i <= 4321; i++){ // g_iGlobalValue = i; // //模擬一個時間較長的處理過程 // for (int j = 0; j < 1000; j++); // } // // //咱們的要求是寫入的最後數值應該爲4321,全局變量未被保護 // //其中線程可能讀取0-4321的中間值,這不是咱們想要的結果! // _tprintf(_T("Timestamp[%u]:Thread[ID:0x%x]寫入數據值爲%d\n"), // GetTickCount(), GetCurrentThreadId(), g_iGlobalValue); // return 0; //} ////////////////////////////////////////////////////////////////////////// DWORD WINAPI ReadThread(PVOID pParam) { //以共享的訪問讀 __try{ AcquireSRWLockShared(&g_sl); //讀出來的全局變量要麼是0,要麼是4321。不可能有其餘值 //當讀線程第1個被調度時,會讀到0.但一旦寫線程被調度,之後全部的 //讀取的值都會是4321 _tprintf(_T("Timestamp[%u]:Thread[ID:0x%X]讀取全局變量值爲%d\n"), GetTickCount(), GetCurrentThreadId(), g_iGlobalValue); } __finally{ ReleaseSRWLockShared(&g_sl); } return 0; } ////////////////////////////////////////////////////////////////////////// DWORD WINAPI WriteThread(PVOID pParam) { //寫時以排它的方式 __try{ AcquireSRWLockExclusive(&g_sl); for (int i = 0; i <= 4321; i++){ g_iGlobalValue = i; SwitchToThread(); //故意切換到其餘線程,很明顯 //在這個循環的期間,因排它方式 //因此其它訪問鎖的線程會被掛起 //從而沒法讀或寫。 } //咱們的要求是寫入的最後數值應該爲4321,全局變量未被保護 //其中線程可能讀取0-4321的中間值,這不是咱們想要的結果! _tprintf(_T("Timestamp[%u]:Thread[ID:0x%X]寫入數據值爲%d\n"), GetTickCount(), GetCurrentThreadId(), g_iGlobalValue); } __finally{ ReleaseSRWLockExclusive(&g_sl); } return 0; }
(5)SRWLock鎖與其餘鎖的比較——實驗:對同一個全局變量加不一樣類型鎖,同時讓每一個線程進行1000000次讀寫性能的比較(時間單位爲ms)
線程數 |
Volatile 讀取 |
Volatile 寫入 |
InterLocked 遞增 |
臨界區 |
SRWLock 共享 |
SRWLock 排它 |
互斥量 Mutex |
1 |
39 |
42 |
57 |
84 |
86 |
88 |
1098 |
2 |
42 |
54 |
80 |
126 |
150 |
120 |
5774 |
4 |
95 |
119 |
161 |
283 |
236 |
236 |
11589 |
①InterLockedIncrement比Volatile讀寫慢是由於CPU必須鎖定內存,但InterLocked函數只能同步一些簡單的整型變量。
②臨界區與SRWLock鎖效率差很少,但建議用SRWLock代替臨界區,由於該鎖容許多個線程同時讀取,對那些只須要讀取共享資源線程來講,這提升了吞吐量
③內核對象的性能最差,這是由於等待、釋放互斥量須要在用戶模式與內核模式之間切換。
④若是考慮性能,首先應該嘗試不要共享數據,而後依次是Volatile讀寫、InterLock函數、SRWLock鎖、臨界區。當這些條件都不知足時,再使用內核對象。
【UserSyncCompare程序】比較不一樣類型鎖的性能
/*********************************************************************** Module: UserSyncCompare.cpp Notices: Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre ***********************************************************************/ #include "../../CommonFiles/CmnHdr.h" #include <windows.h> #include <stdio.h> #include <tchar.h> #include <locale.h> ////////////////////////////////////////////////////////////////////////// // 晶振計時類,基於主板晶振頻率的時間戳計數器的時間類(見第7章) class CStopWatch { public: CStopWatch() { QueryPerformanceFrequency(&m_liPerfFreq); Start(); } void Start() { QueryPerformanceCounter(&m_liPerfStart); } //返回計算自調用Start()函數以來的毫秒數 __int64 Now() const { LARGE_INTEGER liPerfNow; QueryPerformanceCounter(&liPerfNow); return(((liPerfNow.QuadPart - m_liPerfStart.QuadPart) * 1000) / m_liPerfFreq.QuadPart); } private: LARGE_INTEGER m_liPerfFreq; // Counts per second LARGE_INTEGER m_liPerfStart; // Starting count }; ////////////////////////////////////////////////////////////////////////// DWORD g_nIterations = 1000000; //疊代次數 typedef void(CALLBACK* OPERATIONFUNC)(); DWORD WINAPI ThreadInterationFunction(PVOID operationFunc){ OPERATIONFUNC op = (OPERATIONFUNC)operationFunc; for (DWORD iteration = 0; iteration < g_nIterations;iteration++){ op(); } return 0; } ////////////////////////////////////////////////////////////////////////// //MeasureConcurrentOperation函數 //功能:測試一組並行線程的性能 //參數:szOperationName——操做的名稱 // nThreads——測試線程的數量 // ofnOperationFunc——具體以哪一種方式讀、寫操做的函數 void MeasureConcurrentOperation(TCHAR* szOperationName,DWORD nThreads, OPERATIONFUNC opfOperationFunc) { HANDLE* phThreads = new HANDLE[nThreads]; //將當前線程的優先級提到「高」 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); for (DWORD i = 0; i < nThreads;i++){ phThreads[i] = CreateThread(NULL, 0, ThreadInterationFunction, //線程函數 opfOperationFunc,//線程函數的參數(是個函數指針) 0, //當即運行 NULL); } SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); CStopWatch watch; //等待全部線程結束 WaitForMultipleObjects(nThreads, phThreads, TRUE, INFINITE); __int64 elapsedTime = watch.Now(); _tprintf(_T("線程數=%u,毫秒=%u,測試類型=%s\n"), nThreads,(DWORD)elapsedTime,szOperationName); //別忘了關閉全部的線程句柄,並刪除線程數組 for (DWORD i = 0; i < nThreads; i++){ CloseHandle(phThreads[i]); } delete phThreads; } ////////////////////////////////////////////////////////////////////////// //測試列表: //一、不加同步鎖,對整型volatile變量直接讀取 //二、使用InterlockedIncrement對整型變量進行寫操做 //三、使用臨界區對整型volatile變量進行讀取 //四、使用SRWLock鎖對整型volatile變量進行讀寫操做 //五、使用互斥對象Mutex對整型volatile變量進行讀取操做 volatile LONG gv_value = 0; //一、直接讀寫volatile型的整型變量 //'lValue':是個局部變量,己初始化,但未被引用,編譯器 //會出現警告,可禁用該警告 #pragma warning(disable:4189) void WINAPI VolatileReadCallBack() { LONG lValue = gv_value; } #pragma warning(default:4189) void WINAPI VolatileWriteCallBack() { gv_value = 0; } //二、使用InterlockedIncrement對整型變量進行寫操做 void WINAPI InterlockedIncrementCallBack() { InterlockedIncrement(&gv_value); } //三、使用臨界區對整型volatile變量進行讀取 CRITICAL_SECTION g_cs; void WINAPI CriticalSectionCallBack() { EnterCriticalSection(&g_cs); gv_value = 0; LeaveCriticalSection(&g_cs); } //四、使用SRWLock鎖對整型volatile變量進行讀寫操做 SRWLOCK g_srwLock; void WINAPI SRWLockReadCallBack() { AcquireSRWLockShared(&g_srwLock); gv_value = 0; ReleaseSRWLockShared(&g_srwLock); } void WINAPI SRWLockWriteCallBack() { AcquireSRWLockExclusive(&g_srwLock); gv_value = 0; ReleaseSRWLockExclusive(&g_srwLock); } //五、使用互斥對象Mutex對整型volatile變量進行讀取操做 HANDLE g_hMutex; void WINAPI MutexCallBack() { WaitForSingleObject(g_hMutex, INFINITE); gv_value = 0; ReleaseMutex(g_hMutex); } ////////////////////////////////////////////////////////////////////////// int _tmain() { _tsetlocale(LC_ALL, _T("chs")); //分別測試當線程總數爲一、二、4時各類鎖切換所花費的時間 for (int nThreads = 1; nThreads <= 4;nThreads *= 2){ //一、直接讀寫 MeasureConcurrentOperation(_T("Volatile Read"), nThreads, VolatileReadCallBack); MeasureConcurrentOperation(_T("Volatile Write"), nThreads, VolatileWriteCallBack); //二、InterlockedIncrement MeasureConcurrentOperation(_T("Interlocked Increment"), nThreads, InterlockedIncrementCallBack); //三、臨界區: InitializeCriticalSection(&g_cs);//初始化臨界區 MeasureConcurrentOperation(_T("Critical Section"), nThreads, CriticalSectionCallBack); DeleteCriticalSection(&g_cs); //四、SRWLock鎖: InitializeSRWLock(&g_srwLock);//初始化SRWLock鎖,注意不須要釋放,系統會自行回收 MeasureConcurrentOperation(_T("SRWLock Read"), nThreads, SRWLockReadCallBack); MeasureConcurrentOperation(_T("SRWLock Write"), nThreads, SRWLockWriteCallBack); //五、互斥對象: g_hMutex = CreateMutex(NULL, false, NULL);//準備互斥對象 MeasureConcurrentOperation(_T("Mutex"), nThreads, MutexCallBack); CloseHandle(g_hMutex); _tprintf(_T("\n")); } return 0; }