本系列意在記錄Windwos線程的相關知識點,包括線程基礎、線程調度、線程同步、TLS、線程池等。
Slim讀/寫鎖
SRWLock的目的和關鍵段相同,對一個資源進行保護,構造了一段“原子訪問”的代碼,不讓其他線程訪問它。但與關鍵段不同的是SRWLock允許區分想要讀取資源值的線程和想要寫入資源值的線程,因為僅僅讀取資源是不會破壞數據的,下面是Slim讀/寫鎖的簡單用法:
SRWLOCK g_srwLock ... //init SRWLock InitializeSRWLock(&g_srwLock); ... //當需要寫入資源的時候申請"排他鎖" AcquireSRWLOckExclusive(&g_srwLock); //執行寫入動作 ... //寫入結束后釋放"排他鎖" ReleaseSRWLockExclusive(&g_srwLock); //----------------------------------------- //當需要讀取時申請"共享鎖" AcquireSRWLockShared(&g_srwLock); //執行讀操作 ... //讀取結束后釋放"共享鎖" ReleaseSRWLockShared(&g_srwLock); //系統會自動清理g_srwLock,沒有別的清理函數
可以看到,存在兩種鎖用於此機制:"排他鎖"和"共享鎖"。排他鎖需要任何中鎖都不存的情況下才能返回,否則阻塞;共享鎖在沒有排他鎖的情況下就可以返回,哪怕有其他共享鎖也沒關系。這樣在寫操作之前申請排他鎖,在讀之前申請共享鎖就可以保證共享資源數據不會被意外破壞。另外,只有初始化函數而沒有釋放函數。
看似SRWLock更關鍵段十分相似,但相比關鍵段SRWLock缺乏下面兩個特性:
1、不存在對應的TryEnterXXX函數,如果鎖已被占用那么只能選擇阻塞調用線程;
2、不能遞歸的獲得SRWLock。也就是說一個線程不能為了多次寫入資源而多次鎖定資源。而關鍵段可以做到。回想一下,因為關鍵段在Enter的時候將判斷當前線程是否是共享資源的占有者,如果是則會“放行”,並增加引用計數。然而SRWLock始終不關心調用線程是誰。
SRWLock配合條件變量
SRWLock的另一個有用之處就是能配合“條件變量”使用,來完成一些更復雜的同步任務。我們假設如下場景:有一個共享的隊列,2個服務端線程負責讀取隊列中的條目以處理,2個客戶端線程負責寫入隊列中的條目以使服務先端線程處理,當隊列中沒有條目的時候應當掛起服務端線程,直到有條目進入時才被喚醒,另一方面,當隊列已滿時,客戶端線程應當掛起直到服務端至少處理了一個條目,以釋放至少一個條目的空間。
首先創建幾個共享資源鎖,其中SRWLOCK是上文提到的讀寫鎖,CONDITION_VARIABLE就是這里的條件變量:
SRWLOCK g_srwLock; CONDITION_VARIABLE g_cvReadyToProduce;//讀取線程用於通知寫入線程可以開始寫入 CONDITION_VARIABLE g_cvReadyToConsume;//寫入線程用於通知讀取線程可以開始讀取
設計如下客戶端寫入線程流程:
AcquireSRWLockExclusive(&g_srwLock):在寫入之前獲得排他鎖,如果有其他鎖,則阻塞;
SleepConditionVariableSRW(&g_cvReadToProduce,&g_srwLock,INFINITE,0):當隊列已滿時,等待g_cvReadToProduce變量信號(此信號應該由做讀取操作的服務端線程發起)。參數:&g_srwLock,INFINITE,0 分別表示暫時釋放g_srwLock鎖,並永久等待變量信號;
Write Queue...:對隊列的寫操作,如果在上一步時經過Sleep,並臨時釋放了g_srwLock鎖,在這一步會自動重新獲得g_srwLock鎖;
ReleaseSRWLockExclusive(&g_srwLock):寫完隊列后釋放排他鎖;
WakeAllConditionVariable(&g_cvReadToConsume):向所有正在等待隊列中條目的服務端線程發起g_cvReadToConsume信號,通知他們開始讀取隊列;
設計如下的服務端讀取流程:
AcquireSRWLockShared(&g_srwLock):在寫入之前獲得共享鎖,如果有排他鎖,則會阻塞;
SleepConditionVariableSRW(&g_cvReadToConsume,&g_srwLock,INFINITE,CONDITION_VARIABLE_LOCKMODE_SHARED):當隊列空時,等待g_cvReadToConsume變量信號(此信號應該由做寫入操作的客戶端線程發起)。參數:&g_srwLock,INFINITE,CONDITION_VARIABLE_LOCKMODE_SHARED 分別表示共享g_srwLock鎖(不釋放),並永久等待變量信號;
Read Queue...:對隊列的讀操作;
ReleaseSRWLockShared(&g_srwLock):讀完隊列后釋放共享鎖;
WakeAllConditionVariable(&g_cvReadToProduce):向所有正在等待隊列至少有一個空間條目的客戶端線程發起g_cvReadToProduce信號,通知他們可以開始寫入隊列;
上面的過程中我們對一個場景做了設計用到了SRWLock和所謂的條件變量,總結一下條件變量的用法如下:
CONDITION_VARIABLE g_cvReadyToProduce; //創建變量 VOID WINAPI InitializeConditionVariable( __out PCONDITION_VARIABLE ConditionVariable ); //等待變量信號 BOOL WINAPI SleepConditionVariableSRW( __inout PCONDITION_VARIABLE ConditionVariable, __inout PSRWLOCK SRWLock, __in DWORD dwMilliseconds, __in ULONG Flags ); //發出變量信號,以喚醒正在等待變量的線程 VOID WINAPI WakeAllConditionVariable( __inout PCONDITION_VARIABLE ConditionVariable );
前面一篇講到關鍵段的時候也提到過條件變量,事實上,關鍵段也可以使用條件變量,只是API不太相同:
BOOL WINAPI SleepConditionVariableCS( __inout PCONDITION_VARIABLE ConditionVariable, __inout PCRITICAL_SECTION CriticalSection, __in DWORD dwMilliseconds ); VOID WINAPI WakeAllConditionVariable( __inout PCONDITION_VARIABLE ConditionVariable );
另外,除了WakeAllConditionVariable還有一個WakeConditionVariable,顧名思義,后者是單單喚醒一個正在等待變量的線程,這類似於”事件的自動重置”。
關於更多條件變量的信息可以參見:Using Condition Variables
勞動果實,轉載請注明出處:http://www.cnblogs.com/P_Chou/archive/2012/06/24/slim-rw-read-in-thread-sync.html


