【问题标题】:What are common uses of condition variables in C++?C++ 中条件变量的常见用途是什么?
【发布时间】:2010-03-19 09:45:19
【问题描述】:

我正在尝试了解条件变量。我想知道使用条件变量的常见情况有哪些。

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

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

不过,我更喜欢基于经验的示例,例如实际应用中的示例。

【问题讨论】:

  • 这应该是社区维基吗?
  • 因为您可能不是在寻找特定的答案,而只是寻找更一般的“答案”。但我更倾向于这个,你可能想等待别人的否定或否定。
  • +1 尽可能为这个好问题获取那些来之不易的声誉点数:-)
  • @GMan:是的,我实际上是在寻找答案列表,更像是一个汇编。所以,我等着……
  • 嘿,我可以知道我投反对票的原因吗?

标签: c++ multithreading condition-variable


【解决方案1】:

条件变量的一个用途比消息队列复杂一点,它是“共享锁”,其中不同的线程正在等待具有相同基本性质的细微不同的条件。例如,您有一个(非常简陋、简化的)网络缓存。缓存中的每个条目都有三种可能的状态:不存在、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 本身没有定义的错误代码,因此实现它可能会失败不会给您的调用者留下任何好的选择...

当然,对于这种模式,您必须小心缩放。每次任何初始化完成时,每个等待线程都会唤醒以获取锁。因此,当您设计系统时,您会非常仔细地考虑分片,然后决定在发现已证明的性能问题之前,您不会费心做任何事情来实际实施它。

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • @Michael:我对障碍不太熟悉......但在你的例子中,你是说你使用了带有单独条件变量的障碍吗?我以为你可以单独使用障碍来实现上述情况,而不需要单独的条件变量?
    • @jasonline,因此,您可以使用 pthreads 提供的屏障,但您也可以使用互斥锁和条件变量来实现自己的屏障(pthreads 最有可能提供自己的屏障)。当你进入屏障时,你锁定互斥体,增加计数,并存储屏障感知。如果计数已达到最大值,则将计数重置为零,反转屏障感应,发出条件信号并退出屏障。否则,您等待条件,直到障碍感与存储的相反。
    【解决方案3】:

    我知道这不是很有帮助,但是任何时候我希望线程等待某事发生,或者只等到某事发生时,我都会使用条件变量。

    我使用条件变量的一个非常常见的模式是一个后台线程,它每隔几分钟就会唤醒以进行一些处理,然后重新进入睡眠状态。在关闭主线程时通知后台线程完成,然后加入它完成。后台线程等待条件超时,以执行其睡眠。

    后台线程遵循这个基本逻辑

    void threadFunction() {
        initialisation();
    
        while(! shutdown()) {
            backgroundTask();
    
            shutdown_condition_wait(timeout_value);
        }
    
        cleanup();
    }
    

    这可以让后台线程迅速而优雅地关闭。

    如果我有许多这样的线程,主函数会发出信号关闭每个线程,然后在下一个之后加入每个线程。这使每个线程组件能够并行关闭。

    【讨论】:

      【解决方案4】:

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

      Condvars 也可以代替信号量,因为它们更通用。

      【讨论】:

        【解决方案5】:

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-12-12
          • 1970-01-01
          • 1970-01-01
          • 2011-09-15
          • 2011-04-01
          • 2020-03-10
          • 2010-12-21
          相关资源
          最近更新 更多