標籤:des style blog class c tar
詳細見MSDN:http://msdn.microsoft.com/en-us/library/windows/desktop/ms686903%28v=vs.85%29.aspx
我們已經看到,當想讓寫入者線程和讀取者線程以獨佔的方式或共用的方式訪問一個資源的時候,可以使用SRWLock。在這些情況下,如果讀取者線程 沒有資料可以讀取,那麼它應該將鎖釋放並等待,直到寫入者線程產生了新的資料為止。如果用來接收寫入者線程產生的資料結構已滿,那麼寫入者同樣應該釋放 SRWLock並進入睡眠狀態,直到讀取這線程把資料結構清空為止。
我們希望線程以原子的方式把鎖釋放並將自己阻塞,直到某一個條件成立為止。要實現這樣的線程同步是比較複雜的。Windows通過 SleepConditionVariableCS(critical section) 或者SleepConditionVariableSRW 函數,提供了一種 條件變數 協助我們完成這項工作。
當線程檢測到相應的條件滿足的時候(比如,有資料供讀取者使用),它會調用 WakeConditionVariable 或 WakeAllConditionVariable,這樣阻塞在Sleep*函數中的線程就會被喚醒。
先來看一段代碼:
[cpp] view plaincopy
- // 條件變數.cpp : 定義控制台應用程式的進入點。
- //
-
- #include "stdafx.h"
- #include <windows.h>
- #include <iostream>
- #include <vector>
- #include <process.h>
- using namespace std;
-
- DWORD WINAPI ThreadProduce(PVOID pvParam);
- DWORD WINAPI ThreadUser1(PVOID pvParam);
- DWORD WINAPI ThreadUser2(PVOID pvParam);
- vector<int> ivec;
- SRWLOCK g_lock;
- SRWLOCK g_lock2;
- CONDITION_VARIABLE g_ConditionVar;
-
- int _tmain(int argc, _TCHAR* argv[])
- {
- InitializeSRWLock(&g_lock);//初始化鎖
-
- //建立生產者
- HANDLE hThread1 = (HANDLE)_beginthreadex(NULL,0,(unsigned int(_stdcall *)(void*))ThreadProduce,NULL,0,0);
-
- //建立讀取者線程
- HANDLE hThread2 = (HANDLE)_beginthreadex(NULL,0,(unsigned int(_stdcall *)(void*))ThreadUser1,NULL,0,0);
- HANDLE hThread3 = (HANDLE)_beginthreadex(NULL,0,(unsigned int(_stdcall *)(void*))ThreadUser2,NULL,0,0);
- CloseHandle(hThread1);
- CloseHandle(hThread2);
- CloseHandle(hThread3);
-
- system("pause");
- return 0;
- }
-
- DWORD WINAPI ThreadProduce(PVOID pvParam)
- {
- for(int i=0; i<10; ++i)
- {
- AcquireSRWLockExclusive(&g_lock); //獲得SRW鎖
-
- ivec.push_back(i);
-
- ReleaseSRWLockExclusive(&g_lock); //釋放SRW鎖
-
- WakeConditionVariable(&g_ConditionVar); //因為,每次執行push_back 後,容器裡就會必定至少有一個元素(生產者
- //生產出東西了)這時候阻塞在Sleep*裡的線程被喚醒 (讀取者sleep的線程)。
- Sleep(1000);//停一下,讓讀取者先讀 如果不等待,可能會導致vector empty before pop錯誤
- }
- return 0;
- }
-
- DWORD WINAPI ThreadUser1(PVOID pvParam)
- {
- while(1)
- {
- AcquireSRWLockExclusive(&g_lock);
-
- while(ivec.empty())
- {
- cout<<"等待寫入"<<endl;
-
- //如果容器是空的,也就是沒有內容可以讀,那麼讓線程進入睡眠狀態,一直到調用WakeConditionAllVariable(&g_ConditionVar);
- SleepConditionVariableSRW(&g_ConditionVar,&g_lock,INFINITE,CONDITION_VARIABLE_LOCKMODE_SHARED);
-
- }
-
- cout<<"線程1:"<<ivec.back()<<endl;
- ivec.pop_back();
-
- ReleaseSRWLockExclusive(&g_lock);
-
- }
- return 0;
- }
-
- DWORD WINAPI ThreadUser2(PVOID pvParam)
- {
- while(1)
- {
- AcquireSRWLockExclusive(&g_lock);
-
-
- while(ivec.empty())
- {
- cout<<"等待寫入"<<endl;
- SleepConditionVariableSRW(&g_ConditionVar,&g_lock,INFINITE,CONDITION_VARIABLE_LOCKMODE_SHARED);
-
- }
-
- cout<<"線程2:"<<ivec.back()<<endl;
- ivec.pop_back();
-
- ReleaseSRWLockExclusive(&g_lock);
-
-
- }
- return 0;
- }
分析一下這段代碼,其實很簡單,具體可以從代碼注釋中看。之前,寫這段代碼的時候犯過幾個錯誤:
(1)AcquireSRWLockExclusive() ReleaseSRWLockExclusive() 和 AccquireSRWLockShare() ReleaseSRWLockShare() 兩對函數之間的區別
前者獲得的對保護資源的 獨佔 訪問權 而後者獲得是 保護資源的 共用訪問權,因為雖然 代碼裡的讀線程,在讀取資料的同時,他也pop_back()了容器裡的內容,也就是可以看作是“寫”,因為,我們必須獲得的是 獨佔 訪問權。
(2)WakeConditionVariabel() 和 WakeAllconditionVariable() 的區別:
當調用 前者的時候,會使一個在 SleepConditionVariable*函數中等待同一個條件變數被觸發的線程得到鎖並返回。當這個線程釋放同一個鎖的時候,不會喚醒其他正在等待同一個條件變數的線程
當調用後者的時候,會使一個或幾個在SleepConditionVariable*函數中等待這個條件變數觸發的線程達到對資源的訪問權並返回。
(3)
BOOL WINAPI SleepConditionVariableSRW( __in_out PCONDITION_VARIABLE ConditionVariable, //線程休眠相關的條件變數 __in_out PSRWLOCK SRWLock, //指向一個SRWLock的指標 __in DWORD dwMilliseconds, //希望等待的時間,可以為INFINITE __in ULONG Flags);
該函數一原子操作的方式執行了兩個操作:
1.釋放SRWLock指向的鎖;
2.把線程休眠。
如果參數Flags是 CONDITION_VARIABLE_LOCKMODE_SHARED,那麼在同一時刻可以允許多個讀取者線程得到鎖。