Condition variables —— 条件变量,是Windows Vista中新增加的一种处理线程同步问题的机制。它可以与“关键代码段(critical section)”或“读写锁(SRWLock)”相互配合使用,来实现线程的同步,特别是实现类似“生产者-消费者”问题的时候,十分有效。
如果当前没有“产品”可供“消费者线程”(读者线程)使用,那么该“消费者线程”要释放掉对应的读写锁或者关键代码段,然后等待直到有一个新的“产品”被“生产者线程”(写者线程)制造出来之后,方可继续运行。
如果一个用来存放“产品”的数据结构满了(比如数组),那么对应“生产者线程”需要释放有关的锁和关键代码段,同时要等待“消费线程者”消费完这些“产品”。
条件变量机制就是为了简化上述“生产者-消费者”问题而设计的一种线程同步机制。当一个线程需要以原子的方式释放加在资源上的锁,并且需要被阻塞运行直到一个条件被满足,你可以呼叫如下的函数:
PCONDITION_VARIABLE pConditionVariable,
PCRITICAL_SECTION pCriticalSection,
DWORD dwMilliseconds);
PCONDITION_VARIABLE pConditionVariable,
PSRWLOCK pSRWLock,
DWORD dwMilliseconds,
ULONG Flags);
正如函数名所预示的那样,第一个函数针对关键代码段,第二个函数针对读写锁。
第1个参数pContidionVariable参数指向一个初始化的条件变量,该条件变量指明了调用者(一个线程)的条件变量,参数类型是CONDITION_VARIABLE的指针。
第2个参数指明了一个“关键代码段”或“读写锁”,它们是用来保护共享资源的。
第3个参数dwMilliseconds指明了你的线程想要等待多长时间,你可以传递INFINITE,指明需要无限期等待下去。
第二个函数的第4个参数Flags指明当条件变量满足的时候,你需要对应的锁做如何的要求(即返回的时候设置锁,该锁应该是什么类型的):在“生产者线程”(写者线程)中你应该传递0给该参数指明该锁是一个“排他锁”,该锁被线程独占;在“消费者线程”(读者线程)中你应该传递CONDITION_VARIABLE_LOCKMODE_SHARED给该参数指明该锁是一个“共享锁”,该锁能以共享的方式为“消费者线程”服务。
在该参数调用的时候,第二个参数所指定的关键代码段或读写锁会被释放,使得对应的线程可以访问共享资源,从而去“生产”或“消费”;在该函数返回的时候,这个锁又会被设置。如果是SRWLock,该函数返回的时候根据Flags参数设置读写锁类型:排他锁或共享锁。关键代码段则会被自动设置,因为关键代码段总是“排他”的。
如果等待超时,该函数返回FALSE,否则返回TRUE。
一个线程当被SleepConditionVariableCS或SleepConditionVariableSRW阻塞之后,可以被另一个线程通过呼叫WakeConditionVariable或WakeAllConditionVariable函数唤醒。
PCONDITION_VARIABLE ConditionVariable); // 条件变量指针
PCONDITION_VARIABLE ConditionVariable); // 条件变量指针
当你呼叫WakeConditionVariable函数的时候,传递一个条件变量的指针给它,此时,在一个等待在同样条件变量的上的线程的SleepConditionVariable函数内部,条件变量收到信号,通知线程,该函数会返回,同时把对应的锁设定为所需要的类型。
当你呼叫WakeAllConditionVarialbe函数的时候,一个过多个等待在相同条件变量上的线程会被被唤醒。唤醒多个线程是可以的,但是你需要在调用SleepConditionVariable*函数的时候设定参数Flags:给“生产者线程”传递0;给“消费者线程”传递CONDITION_VARIABLE_LOCKMODE_SHARED。所以,有些时候,“消费者线程”全部会被唤醒。或者这样唤醒:生产者、消费者、生产者、消费者……如此循环。
本书还举了一个例子,这里就不多说了,我个人总结了下,运用条件变量应该遵循如下模式(自己是这么认为的,如果有误还大家请指出)
CONDITION_VARIABLE g_cvProduce; //生产条件变量
CONDITION_VARIABLE g_cvConsume; //消费条件变量
SRWLOCK g_srwLock; //读写锁
DWORD WINAPI Consumer(PVOID pvParam) //消费者线程函数
{
AcquireSRWLockShard(&g_srwLock); //请求共享锁(读锁)
SleepConditionVariableSRW(g_cvConsume, &g_srwLock, INFINITE, CONDITION_VARIABLE_LOCKMODE_SHARED); //等待条件变量,会被生产者线程唤醒
//消费
ReleaseSRWLockShared(&g_srwLock); //释放共享锁
WakeConditionVariable(&g_cvProduce); //唤醒一个生产者线程
}
DWORD WINAPI Producer(PVOID pvParam) //生产者线程函数
{
AcquireSRWLockExclusive(&g_srwLock); //要求一个排他锁(写锁)
//等待条件变量受信,会被消费者线程唤醒
SleepConditionVariableSRW(g_cvProduce, &g_srwLock, INFINITE, 0);
//生产
ReleaseSRWLockExclusive(&g_srwLock); //释放排他锁
WakeAllConditionVariable(&g_cvConsume); //唤醒所有消费者线程
}