std::condition_variable wait() 和 notify_one() 同步

Posted

技术标签:

【中文标题】std::condition_variable wait() 和 notify_one() 同步【英文标题】:std::condition_variable wait() and notify_one() synchronization 【发布时间】:2015-11-20 10:37:00 【问题描述】:

前言:我在这里看到过类似的问题,但似乎没有一个回答我的问题。

是否有可靠的方法来确保消费者线程中的 wait() 方法在生产者线程第一次调用 notify_one() 之前被调用?

即使在消费者线程中有unique_lock,也有可能生产者线程会先运行,锁定互斥体并在消费者调用wait()之前调用notify(),因此,我的应用程序将首先丢失@ 987654327@电话。

编辑:感谢您的所有回答,他们确实帮助了我。我的问题是这个消费者循环中的第一个 wait-notify() :

while (!timeToQuit) 
    gdcv.wait(gdcondlock);
    gdlock.lock();
    //spurious wakeup
    if (gdQueue.empty()) 
      gdlock.unlock();
      continue;
    
    //some work here
    gdlock.unlock();
 

我想我必须为第一次循环迭代编写额外的代码。

EDIT2:这个循环和第二个锁(unique_lock btw)之所以存在,是因为有多个生产者和消费者访问队列。

EDIT3:在boost::lockfree::queue 的帮助下等待这个特定线程的正确方法,以防万一有人遇到类似问题:

  nfq_data* data;
  while (!timeToQuit) 
    gdcv.wait(gdlock,[&]return !gdQueue.empty() || timeToQuit;);
    gdQueue.pop(data);
    gdlock.unlock();
  

【问题讨论】:

听起来几乎就像您需要的是信号量而不是条件变量 - 例如,请参见此处:***.com/questions/4792449/… 你为什么打电话给gdlock.lock()gdlock.unlock()而不是lock_guard?你不喜欢简单和正确吗? 为什么你还有两把锁?听起来您甚至不需要问代码是否更惯用的问题 不能用lock_guard lg(m); gdcv.wait(lg, [&] return gdQueue.empty(); )替换整个东西吗?这应该做正确的事情,即如果条件为真则立即返回,或者等到通知并且条件为真。 您可能有一个非常特殊的情况,但在正常情况下,不需要无锁容器。当容器被修改、通知和等待时,应该使用相同的互斥锁。 【参考方案1】:

即使在消费者线程中使用 unique_lock,生产者线程也有可能首先运行,锁定互斥体并在消费者调用 wait() 之前调用 noify(),因此,我的应用程序将丢失第一个 nofity() 调用。

消费者要么有东西要等待,要么没有。如果它有什么要等待的,那就没有问题了。如果没有任何等待,请不要致电wait。真的就是这么简单。

当且仅当您想等待时致电wait

存在条件变量来解决如何释放锁并等待而不冒等待已经发生的事情的风险的问题。他们通过提供一个原子地释放锁并等待的函数来解决这个问题。他们不会错过唤醒,因为他们决定睡觉时会持有锁。

【讨论】:

【参考方案2】:

听起来您正试图(错误地)使用condition_variable 来实现“屏障”。

条件变量允许您等待某些 条件 变为真,通过某些谓词进行测试,例如“有可用的工作”,并且您应该始终在等待之前测试谓词,以确保您不会“错过”事件并在应该工作的时候等待。

纯粹使用条件变量等待,没有关联的谓词,效果不好。这不是它们的设计用途。

如果您试图让所有线程在代码中的特定点等待,并且仅在它们都到达时才继续,那么您使用的概念略有不同,称为屏障。

C++ 并发 TS 为 C++ 标准库定义了障碍(以及稍微简单的“闩锁”概念),请参阅草案N4538。

您可以通过定义一个带有计数器的类来自己定义一个屏障,该计数器在内部使用一个 condition_variable。它需要等待的条件是“所有 N 个线程都增加了计数器”。然后你可以让生产者和所有消费者在屏障处等待,他们都会阻塞,直到最后一个线程到达屏障。即使生产者到达屏障并开始等待,您也可以保证消费者也会停止并在屏障处等待,直到所有线程都到达它,然后它们都会继续。

【讨论】:

【参考方案3】:

不,由您负责线程同步。

如果你不想错过一个通知调用,即使它发生在消费者开始等待之前,你必须控制这种可能性,在某处记录producer完成了,然后根本不调用wait()函数。

例如,您可以实现一种事件类,仅当事件尚未发生时才等待条件变量:

#include <mutex>
#include <condition_variable>

class Event

public:
    Event();
    void set_event();
    void reset_event();
    void wait_event();
private:
    std::mutex mtx;
    std::condition_variable cv;
    bool is_set;
;

Event::Event()
: is_setfalse


void Event::set_event()

    std::lock_guard<std::mutex> lckmtx;
    is_set = true;
    cv.notify_all();


void Event::reset_event()

    std::lock_guard<std::mutex> lckmtx;
    is_set = false;


void Event::wait_event()

    std::unique_lock<std::mutex> lckmtx;
    if( is_set )
        return;

    cv.wait(lck, [this] return is_set; );

【讨论】:

【参考方案4】:

即使在消费者线程中使用 unique_lock,生产者线程也有可能首先运行,锁定互斥体并在消费者调用 wait() 之前调用 noify(),因此,我的应用程序将丢失第一个 nofity() 调用

如果生产者已经运行,那么消费者不必等待,因此不应调用wait。如果消费者只在需要时等待 - 并且条件是同步的 - 那么它就不会错过需要通知的通知。

【讨论】:

以上是关于std::condition_variable wait() 和 notify_one() 同步的主要内容,如果未能解决你的问题,请参考以下文章

std::atomic 和 std::condition_variable 等待、notify_* 方法之间的区别

C++ std::condition_variable wait() wait_for() 区别 怎么用 实例

如何正确使用 std::condition_variable?

std::condition_variable::notify_all() - 我需要一个例子

std::condition_variable 条件变量类型

在等待std :: condition_variable时如何处理系统时钟更改?