我们已经看到,当想让写入者线程和读取者线程以独占的方式或共享的方式访问一个资源的时候,可以使用SRWLock。在这些情况下,如果读取者线程没有数据可以读取,那么它应该将锁释放并等待,直到写入者线程产生了新的数据为止。如果用来接收写入者线程产生的数据结构已满,那么写入者同样应该释放SRWLock并进入睡眠状态,直到读取这线程把数据结构清空为止。
我们希望线程以原子的方式把锁释放并将自己阻塞,直到某一个条件成立为止。要实现这样的线程同步是比较复杂的。Windows通过SleepConditionVariableCS(critical section) 或者SleepConditionVariableSRW 函数,提供了一种 条件变量 帮助我们完成这项工作。
当线程检测到相应的条件满足的时候(比如,有数据供读取者使用),它会调用 WakeConditionVariable 或 WakeAllConditionVariable,这样阻塞在Sleep*函数中的线程就会被唤醒。
先来看一段代码:
// 条件变量.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);//停一下,让读取者先读
}
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,
__in DWORD dwMilliseconds,
__in ULONG Flags);
如果参数Flags是 CONDITION_VARIABLE_LOCKMODE_SHARED,那么在同一时刻可以允许多个读取者线程得到锁。