C++ 为什么我需要 std::condition_variable ?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16350473/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 20:17:03  来源:igfitidea点击:

why do I need std::condition_variable?

c++c++11concurrency

提问by Howard Hinnant

I found that std::condition_variableis very difficult to use due to spurious wakeups. So sometimes I need to set a flags such as:

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

atomic<bool> is_ready;

I set is_readyto truebefore I call notify (notify_one()or notify_all()), and then I wait:

我在调用 notify (或)之前设置is_ready为,然后等待:truenotify_one()notify_all()

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

Is there any reason that I shouldn't just do this: (Edit: Ok, this is really a bad idea.)

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

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

And if condition_variablehad chosen a waiting duration (I don't know whether this is true or not), I prefer choose it myself.

如果condition_variable选择了等待时间(我不知道这是不是真的),我更喜欢自己选择。

回答by Howard Hinnant

You can code this either way:

你可以用任何一种方式编码:

  1. Using atomics and a polling loop.
  2. Using a condition_variable.
  1. 使用原子和轮询循环。
  2. 使用condition_variable.

I've coded it both ways for you below. On my system I can monitor in real time how much cpu any given process is using.

我已经在下面为你编码了两种方式。在我的系统上,我可以实时监控任何给定进程正在使用多少 CPU。

First with the polling loop:

首先是轮询循环:

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

For me this takes 30 seconds to execute, and while executing the process takes about 99.6% of a cpu.

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

Alternatively with a condition_variable:

或者使用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();
}

This has the exact same behavior except that during the 30 second execution, the process is taking 0.0% cpu. If you're writing an app that might execute on a battery powered device, the latter is nearly infinitely easier on the battery.

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

Now admittedly, if you had a very poor implementation of std::condition_variable, it could have the same inefficiency as the polling loop. However in practice such a vendor ought to go out of business fairly quickly.

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

Update

更新

For grins I augmented my condition_variable wait loop with a spurious wakeup detector. I ran it again, and it did not print out anything. Not one spurious wakeup. That is of course not guaranteed. But it does demonstrate what a quality implementation can achieve.

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

回答by Wandering Logic

The purpose of std::condition_variableis to wait for some condition to become true. It is notdesigned to be just a receiver of a notify. You might use it, for example, when a consumer thread needs to wait for a queue to become non-empty.

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

The consumer (get_from_queue) is notwaiting for the condition variable, they are waiting for the condition the_queue.empty(). The condition variable gives you the way to put them to sleep while they are waiting, simultaneously releasing the mutex and doing so in a way that avoids race conditions where you miss wake ups.

消费者 ( get_from_queue)不是在等待条件变量,而是在等待条件the_queue.empty()。条件变量使您可以在他们等待时让他们进入睡眠状态,同时释放互斥锁并以一种避免您错过唤醒的竞争条件的方式这样做。

The condition you are waiting on should be protected by a mutex (the one you release when you wait on the condition variable.) This means that the condition rarely (if ever) needs to be an atomic. You are always accessing it from within a mutex.

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