为什么我需要std :: condition_variable?

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

atomic<bool> is_ready;

我在致电notify(notify_one()notify_all())之前将condition_variable设置为true,然后等待:

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选择了等待时间(我不知道这是否成立),我宁愿自己选择。

Yu Hao asked 2020-01-17T22:31:10Z
2个解决方案
83 votes

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

  1. 使用原子和轮询循环。
  2. 使用std::condition_variable

我已经在下面为您提供了两种编码方式。 在我的系统上,我可以实时监视任何给定进程使用了多少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。

或者使用std::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的实现非常差,则它的效率可能与轮询循环相同。 但是实际上,这样的供应商应该很快就倒闭。

更新资料

对于咧嘴笑,我使用伪造的唤醒检测器增强了condition_variable等待循环。 我再次运行它,但它没有打印出任何内容。 没有一个虚假的唤醒。 当然不能保证。 但这确实说明了质量实现可以实现的目标。

Howard Hinnant answered 2020-01-17T22:32:19Z
30 votes

atomic的目的是等待某些条件变为真。 它不旨在仅仅是通知的接收者。 例如,当使用者线程需要等待队列变为非空时,您可以使用它。

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.
}

使用者(atomic)不在等待条件变量,而是在等待条件the_queue.empty()。条件变量为您提供了一种使其在等待时进入睡眠状态的方式,同时释放互斥量并避免这种情况发生。 错过醒来的比赛条件。

您正在等待的条件应受一个互斥锁保护(在条件变量上等待时释放的互斥量)。这意味着该条件很少(如果有的话)必须是atomic。 互斥体。

Wandering Logic answered 2020-01-17T22:32:50Z
translate from https://stackoverflow.com:/questions/16350473/why-do-i-need-stdcondition-variable