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
why do I need std::condition_variable?
提问by Howard Hinnant
I found that std::condition_variable
is 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_ready
to true
before I call notify (notify_one()
or notify_all()
), and then I wait:
我在调用 notify (或)之前设置is_ready
为,然后等待:true
notify_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_variable
had 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:
你可以用任何一种方式编码:
- Using atomics and a polling loop.
- Using a
condition_variable
.
- 使用原子和轮询循环。
- 使用
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_variable
is 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
. 您总是从互斥锁中访问它。