C++ 为什么我需要std::condition\u变量?

C++ 为什么我需要std::condition\u变量?,c++,c++11,concurrency,C++,C++11,Concurrency,我发现由于虚假的唤醒,std::condition_variable很难使用。因此,有时我需要设置一个标志,例如: atomic<bool> is_ready; 有什么理由我不应该这么做:(编辑:好吧,这真是个坏主意。) 如果condition\u variable选择了等待时间(我不知道这是不是真的),我宁愿自己选择。std::condition\u variable的目的是等待某个条件变为真。它并不仅仅是一个通知的接收者。例如,当使用者线程需要等待队列变为非空时,可以使用它 T

我发现由于虚假的唤醒,
std::condition_variable
很难使用。因此,有时我需要设置一个标志,例如:

atomic<bool> is_ready;
有什么理由我不应该这么做:(编辑:好吧,这真是个坏主意。)


如果
condition\u variable
选择了等待时间(我不知道这是不是真的),我宁愿自己选择。

std::condition\u variable的目的是等待某个条件变为真。它并不仅仅是一个通知的接收者。例如,当使用者线程需要等待队列变为非空时,可以使用它

T get_from_queue() {
   std::unique_lock l(the_mutex);
   while (the_queue.empty()) {
     the_condition_variable.wait(l);
   }
   // the above loop is _exactly_ equivalent to the_condition_variable.wait(l, [&the_queue](){ return !the_queue.empty(); }
   // now we have the mutex and the invariant (that the_queue be non-empty) is true
   T retval = the_queue.top();
   the_queue.pop();
   return retval;
}

put_in_queue(T& v) {
  std::unique_lock l(the_mutex);
  the_queue.push(v);
  the_condition_variable.notify_one();  // the queue is non-empty now, so wake up one of the blocked consumers (if there is one) so they can retest.
}
使用者(
get\u from\u queue
)没有等待条件变量,他们正在等待条件
队列.empty()
。condition变量为您提供了在他们等待时让他们进入睡眠状态的方法,同时释放互斥锁,并以一种避免错过唤醒的比赛条件的方式进行操作


您等待的条件应该由互斥锁(在等待条件变量时释放的互斥锁)保护。这意味着该条件很少(如果有)需要是原子的。您总是从互斥锁中访问它。

您可以通过以下两种方式进行编码:

  • 使用原子和轮询循环
  • 使用
    条件变量
  • 我在下面用两种方式为您编写了代码。在我的系统上,我可以实时监控任何给定进程使用了多少cpu

    首先是轮询循环: 除了在30秒的执行过程中,进程占用0.0%的cpu外,它的行为完全相同。如果你正在编写一个可能在电池供电的设备上运行的应用程序,后者在电池上几乎是无限容易的

    诚然,如果您的
    std::condition_variable
    的实现非常糟糕,那么它可能与轮询循环一样低效。然而,在实践中,这样的供应商应该很快停业

    更新


    对于露齿而笑,我用一个虚假的唤醒检测器增加了我的条件变量等待循环。我再次运行它,它没有打印出任何内容。没有一次虚假的觉醒。这当然不能保证。但它确实证明了高质量的实现可以实现什么。

    我已经测试了您的代码,您的说法是正确的。但我想这是因为你的杯子碰巧闲置了。我已将
    此线程::yield()
    替换为
    此线程::sleep\u(chrono::millides(1))
    ,该过程也占用了0.0%的cpu。@Mike:但现在您平均要等待0.5毫秒(~100万个机器周期!),您的消费者才会意识到“准备就绪”现在是真的。使用condition\u变量,您不会消耗cpu,并且可能会在几千个机器周期内收到唤醒和检查条件的通知。@Alex:如果
    main
    test
    实际上处于不同的线程中,
    test
    必须在修改
    是否准备就绪之前锁定互斥锁。@bRadGibson:Yes。第二个示例显然具有低能耗的优点,要实现这一点,您需要
    互斥
    条件变量
    。由于这些都是必需的,因此可以在
    互斥锁下自由操作
    是否准备就绪
    。只有在有明显好处的情况下,才应该拔出称为原子的锋利而危险的刀。在进行代码审查时,当您看到原子代码时,是时候喝杯新鲜咖啡,真正专注于代码了。花一小时而不是5分钟。如果您不需要
    原子变量
    ,请不要让您的代码审阅者付出额外的努力。这个答案是试验
    std::condition\u变量
    的一个很好的起点。我很少会因为新概念而感到如此痛苦,但条件变量的概念是我一直想要的。我很高兴看到可以直接通过
    std
    实现;如果notify调用是在锁定互斥锁的情况下完成的,那么简单的
    bool
    就足够了。
    while(!is_ready) {
        this_thread::wait_for(some_duration); //Edit: changed from this_thread::yield();
    }
    
    T get_from_queue() {
       std::unique_lock l(the_mutex);
       while (the_queue.empty()) {
         the_condition_variable.wait(l);
       }
       // the above loop is _exactly_ equivalent to the_condition_variable.wait(l, [&the_queue](){ return !the_queue.empty(); }
       // now we have the mutex and the invariant (that the_queue be non-empty) is true
       T retval = the_queue.top();
       the_queue.pop();
       return retval;
    }
    
    put_in_queue(T& v) {
      std::unique_lock l(the_mutex);
      the_queue.push(v);
      the_condition_variable.notify_one();  // the queue is non-empty now, so wake up one of the blocked consumers (if there is one) so they can retest.
    }
    
    #include <atomic>
    #include <chrono>
    #include <iostream>
    #include <thread>
    
    std::atomic<bool> is_ready(false);
    
    void
    test()
    {
        std::this_thread::sleep_for(std::chrono::seconds(30));
        is_ready.store(true);
    }
    
    int
    main()
    {
        std::thread t(test);
        while (!is_ready.load())
            std::this_thread::yield();
        t.join();
    }
    
    #include <chrono>
    #include <condition_variable>
    #include <iostream>
    #include <mutex>
    #include <thread>
    
    bool is_ready(false);
    std::mutex m;
    std::condition_variable cv;
    
    void
    test()
    {
        std::this_thread::sleep_for(std::chrono::seconds(30));
        std::unique_lock<std::mutex> lk(m);
        is_ready = true;
        cv.notify_one();
    }
    
    int
    main()
    {
        std::thread t(test);
        std::unique_lock<std::mutex> lk(m);
        while (!is_ready)
        {
            cv.wait(lk);
            if (!is_ready)
                std::cout << "Spurious wake up!\n";
        }
        t.join();
    }