为什么我需要std :: condition_variable?

时间:2013-05-03 01:59:00

标签: c++ c++11 concurrency

我发现由于虚假的唤醒,std::condition_variable非常难以使用。所以有时我需要设置一个标志,如:

atomic<bool> is_ready;

我在致电通知(is_readytrue)之前将notify_one()设置为notify_all(),然后我等待:

some_condition_variable.wait(some_unique_lock, [&is_ready]{
    return bool(is_ready);
});

我有什么理由不这样做:(编辑:好的,这真是一个坏主意。)

while(!is_ready) {
    this_thread::wait_for(some_duration); //Edit: changed from this_thread::yield();
}

如果condition_variable选择了等待时间(我不知道这是否属实),我宁愿自己选择。

2 个答案:

答案 0 :(得分:80)

您可以采用以下两种方式进行编码:

  1. 使用原子和轮询循环。
  2. 使用condition_variable
  3. 我在下面为你编写了两种方式。在我的系统上,我可以实时监控任何给定进程使用的CPU数量。

    首先使用轮询循环:

    #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();
    }
    

    对我来说这需要30秒才能执行,而执行该过程需要大约99.6%的cpu。

    或者使用condition_variable

    #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();
    }
    

    这具有完全相同的行为,除了在30秒执行期间,该过程占用0.0%cpu。如果您正在编写可能在电池供电的设备上执行的应用程序,那么后者在电池上几乎无限容易。

    现在可以肯定的是,如果std::condition_variable的实现非常糟糕,它可能与轮询循环具有相同的低效率。但是在实践中,这样的供应商应该很快就会破产。

    <强>更新

    对于grins,我用一个虚假的唤醒检测器扩充了我的condition_variable等待循环。我再次跑了,它没有打印出任何东西。没有一个虚假的唤醒。这当然不能保证。但它确实证明了质量实施可以实现的目标。

答案 1 :(得分:29)

std::condition_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_from_queue等待条件变量,他们正在等待条件the_queue.empty()。条件变量为您提供了在等待时让它们进入睡眠状态的方法,同时释放互斥锁并以避免因您错过唤醒的竞争条件而这样做。

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