我正在学习条件变量。我想知道条件变量通常用于哪些情况。
一个例子是在阻塞队列中,其中两个线程访问队列 - 生产者线程将项目推入队列,而消费者线程从队列中弹出项目。如果队列为空,则消费者线程会等待生产者线程发送信号。
还有哪些设计情况需要使用条件变量?
我更喜欢基于经验的例子,例如实际应用程序中的例子。
我正在学习条件变量。我想知道条件变量通常用于哪些情况。
一个例子是在阻塞队列中,其中两个线程访问队列 - 生产者线程将项目推入队列,而消费者线程从队列中弹出项目。如果队列为空,则消费者线程会等待生产者线程发送信号。
还有哪些设计情况需要使用条件变量?
我更喜欢基于经验的例子,例如实际应用程序中的例子。
条件变量的一个比消息队列更复杂的用途是“共享锁”,其中不同的线程正在等待相同基本性质的微妙不同的条件。例如,您有一个(非常不可靠、简化的)Web缓存。缓存中的每个条目有三种可能的状态:不存在、IN_PROGRESS、COMPLETE。
getURL:
lock the cache
three cases for the key:
not present:
add it (IN_PROGRESS)
release the lock
fetch the URL
take the lock
update to COMPLETE and store the data
broadcast the condition variable
goto COMPLETE
COMPLETE:
release the lock and return the data
IN_PROGRESS:
while (still IN_PROGRESS):
wait on the condition variable
goto COMPLETE
我曾经使用这种模式来实现POSIX函数pthread_once
的变体,而不需要调度程序的帮助。我不能为每个once_control
使用信号量或锁,并在锁下进行初始化的原因是,该函数不允许失败,并且once_control
只有微不足道的初始化。就此而言,pthread_once
本身没有定义错误代码,因此将其实现为可能失败并不会给您的调用者留下任何好的选择......
当然,使用这种模式时必须小心扩展性问题。每次完成任何初始化时,每个等待的线程都会醒来以获取锁。因此,在设计系统时,您需要非常谨慎地考虑分片,然后决定在看到经过验证的性能问题之前不会采取任何实际实施措施。
除了您已经提到的消费者-生产者模型之外,另一个例子是屏障同步的使用。当线程进入屏障时,如果仍有其他线程需要进入屏障,则它们会等待条件变量。最后一个进入屏障的线程会发出信号。
我知道这并不是很有帮助,但每当我想让一个线程等待某些事情发生或仅等待某些事情发生时,我都会使用条件变量。
我经常使用条件变量的一种非常常见的模式是后台线程每隔几分钟就会唤醒进行一些处理,然后再次进入睡眠状态。在关闭时,主线程向后台线程发出信号以完成并加入完成。后台线程使用超时等待条件来执行其休眠。
后台线程遵循以下基本逻辑:
void threadFunction() {
initialisation();
while(! shutdown()) {
backgroundTask();
shutdown_condition_wait(timeout_value);
}
cleanup();
}
这样可以让后台线程及时、优雅地关闭。
如果我有多个这样的线程,主函数会向每个线程发出关闭信号,然后一个接一个地加入每个线程。这使得每个线程组件可以并行关闭。
我用它来发送同步消息,其中添加了一个同步对象。
同步对象由带有“准备就绪”布尔值的条件变量组成。
在syncMsg::send()函数中,有一个sync->wait(),而在syncMsg::handle()函数中,则有一个sync->go()。
应谨慎使用,因为可能会出现死锁。
我使用条件变量而不是容易出错的Win32事件对象。使用条件变量,您不必太担心虚假信号。等待多个事件发生也更容易。
条件变量还可以替代信号量,因为它们更通用。