在C++中,条件变量的常见用途是什么?

16

我正在学习条件变量。我想知道条件变量通常用于哪些情况。

一个例子是在阻塞队列中,其中两个线程访问队列 - 生产者线程将项目推入队列,而消费者线程从队列中弹出项目。如果队列为空,则消费者线程会等待生产者线程发送信号。

还有哪些设计情况需要使用条件变量?

我更喜欢基于经验的例子,例如实际应用程序中的例子。


1
这应该是社区维基吗? - jasonline
由于您并不寻求特定的答案,而只是更一般性的“答案”,所以可能是这样。但我对此还有些犹豫,您可能需要等待其他人的否定或肯定。 - GManNickG
2
赶紧领取你应得的声望点数,因为这是一道好问题 :-) - Vincent Robert
@GMan:是的,我实际上正在寻找答案列表,更像是一份汇编。所以,我会等待... - jasonline
嘿,可以告诉我为什么我被踩了吗? - jasonline
5个回答

2

条件变量的一个比消息队列更复杂的用途是“共享锁”,其中不同的线程正在等待相同基本性质的微妙不同的条件。例如,您有一个(非常不可靠、简化的)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本身没有定义错误代码,因此将其实现为可能失败并不会给您的调用者留下任何好的选择......

当然,使用这种模式时必须小心扩展性问题。每次完成任何初始化时,每个等待的线程都会醒来以获取锁。因此,在设计系统时,您需要非常谨慎地考虑分片,然后决定在看到经过验证的性能问题之前不会采取任何实际实施措施。


1

除了您已经提到的消费者-生产者模型之外,另一个例子是屏障同步的使用。当线程进入屏障时,如果仍有其他线程需要进入屏障,则它们会等待条件变量。最后一个进入屏障的线程会发出信号。


@Michael:我不太熟悉屏障……但是在你的例子中,你是说你使用了带有单独条件变量的屏障吗?我认为你可以仅使用屏障来实现上述情况,而不需要单独的条件变量? - jasonline
@jasonline,所以你可以使用pthread提供的屏障,但也可以使用互斥锁和条件变量实现自己的屏障(这很可能是pthread自己提供的方法)。当你进入屏障时,锁定互斥锁,递增计数并存储屏障意义。如果计数已达到最大值,则将计数重置为零,反转屏障意义,发出条件信号并退出屏障。否则,您将在条件上等待,直到屏障感觉与存储的感觉相反。 - Michael Aaron Safyan

0

我知道这并不是很有帮助,但每当我想让一个线程等待某些事情发生或仅等待某些事情发生时,我都会使用条件变量。

我经常使用条件变量的一种非常常见的模式是后台线程每隔几分钟就会唤醒进行一些处理,然后再次进入睡眠状态。在关闭时,主线程向后台线程发出信号以完成并加入完成。后台线程使用超时等待条件来执行其休眠。

后台线程遵循以下基本逻辑:

void threadFunction() {
    initialisation();

    while(! shutdown()) {
        backgroundTask();

        shutdown_condition_wait(timeout_value);
    }

    cleanup();
}

这样可以让后台线程及时、优雅地关闭。

如果我有多个这样的线程,主函数会向每个线程发出关闭信号,然后一个接一个地加入每个线程。这使得每个线程组件可以并行关闭。


0

我用它来发送同步消息,其中添加了一个同步对象。
同步对象由带有“准备就绪”布尔值的条件变量组成。
在syncMsg::send()函数中,有一个sync->wait(),而在syncMsg::handle()函数中,则有一个sync->go()。

应谨慎使用,因为可能会出现死锁。


0

我使用条件变量而不是容易出错的Win32事件对象。使用条件变量,您不必太担心虚假信号。等待多个事件发生也更容易。

条件变量还可以替代信号量,因为它们更通用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接