【Windows】線程漫談——線程同步之Slim讀/寫鎖

來源:互聯網
上載者:User

本系列意在記錄Windwos線程的相關知識點,包括線程基礎、線程調度、線程同步、TLS、線程池等。

 

Slim讀/寫鎖

SRWLock的目的和關鍵段相同,對一個資源進行保護,構造了一段“原子訪問”的代碼,不讓其他線程訪問它。但與關鍵段不同的是SRWLock允許區分想要讀取資源值的線程和想要寫入資源值的線程,因為僅僅讀取資源是不會破壞資料的,下面是Slim讀/寫鎖的簡單用法:

SRWLOCK g_srwLock...//init SRWLockInitializeSRWLock(&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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.