我正在尝试了解条件变量。我想知道使用条件变量的常见情况是什么。
一个例子是在一个阻塞队列中,两个线程访问队列——生产者线程将一个项目推入队列,而消费者线程从队列中弹出一个项目。如果队列为空,则消费者线程一直在等待,直到生产者线程发送信号。
还有哪些其他需要使用条件变量的设计情况?
不过,我更喜欢基于经验的示例,例如真实应用程序中的示例。
我正在尝试了解条件变量。我想知道使用条件变量的常见情况是什么。
一个例子是在一个阻塞队列中,两个线程访问队列——生产者线程将一个项目推入队列,而消费者线程从队列中弹出一个项目。如果队列为空,则消费者线程一直在等待,直到生产者线程发送信号。
还有哪些其他需要使用条件变量的设计情况?
不过,我更喜欢基于经验的示例,例如真实应用程序中的示例。
比消息队列更复杂的条件变量的一种用途是“共享锁”,其中不同的线程正在等待具有相同基本性质的细微不同的条件。例如,您有一个(非常简陋、简化的)网络缓存。缓存中的每个条目都有三种可能的状态:不存在、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
而无需调度程序的任何帮助。我不能使用信号量或锁 per once_control
,而只是在锁下进行初始化的原因是该函数不允许失败,并且once_control
只有微不足道的初始化。就此而言,pthread_once
它本身没有定义的错误代码,因此实现它可能会失败不会给你的调用者留下任何好的选择......
当然,对于这种模式,您必须小心缩放。每次任何初始化完成时,每个等待的线程都会唤醒以获取锁。因此,当您设计系统时,您会非常仔细地考虑分片,然后决定在看到已证明的性能问题之前,您不会费心做任何事情来实际实施它。
一个例子,除了你已经提到的消费者 - 生产者模型之外,还有屏障同步的使用。当线程进入屏障时,如果还有其他线程需要进入屏障,那么它们会等待一个条件变量。最后一个进入屏障的线程发出条件信号。
我用它来发送同步消息,其中添加了一个同步对象。
同步对象由一个带有“就绪”布尔值的条件变量组成。
在syncMsg::send() 函数中,有一个sync->wait(),在syncMsg::handle() 函数中,有一个sync->go()。
由于可能出现死锁,应谨慎使用。
我使用条件变量而不是容易出错的 Win32 事件对象。使用 condvars,您不必太担心虚假信号。等待多个事件发生也更容易。
Condvars 也可以代替信号量,因为它们更通用。
我知道这不是很有帮助,但是每当我希望线程等待某事发生或仅等到某事发生时,我都会使用条件变量。
我使用条件变量的一个非常常见的模式是一个后台线程,它每隔几分钟唤醒一次以进行一些处理,然后重新进入睡眠状态。在关闭主线程时通知后台线程完成,然后加入它完成。后台线程等待条件超时,以执行其睡眠。
后台线程遵循这个基本逻辑
void threadFunction() {
initialisation();
while(! shutdown()) {
backgroundTask();
shutdown_condition_wait(timeout_value);
}
cleanup();
}
这可以让后台线程迅速而优雅地关闭。
如果我有许多这样的线程,主函数会发出信号以关闭每个线程,然后在下一个之后加入每个线程。这使每个线程组件能够并行关闭。