std::condition_variable 可预测的虚假唤醒?
std::condition_variable predictable spurious wake-ups?
我 运行 喜欢这种有趣的虚假唤醒行为。考虑这个简单的演示代码:
#include <iostream>
#include <chrono>
#include <thread>
#include <condition_variable>
#include <mutex>
using namespace std; // I know
using namespace std::chrono;
using namespace std::chrono_literals;
mutex mtx; // used for cv and synchronized access to counter
condition_variable cv;
int counter = 0; // (1)
bool keep_running = true; // flag for signaling an exit condition
int main()
{
// thread that decrements counter every time it is woken up and the counter is > 0
thread t([&] {
while (keep_running)
{
unique_lock<mutex> lock(mtx);
cv.wait(lock, [&] {
cout << "Woken up" << endl;
return !keep_running || counter > 0;
});
if (!keep_running) { // check for exit condition
break;
}
--counter;
}
});
this_thread::sleep_for(1s); // some work
unique_lock<mutex> lock(mtx);
counter = 5; // set the counter
cout << "Notifying" << endl;
lock.unlock();
cv.notify_one(); // wake the thread up
this_thread::sleep_for(1s); // some more work
cout << "Exiting" << endl;
lock.lock();
keep_running = false; // ask the thread to exit
lock.unlock();
cv.notify_one(); // wake up one last time
t.join(); // join and exit
cout << "Counter: " << counter << endl;
}
使用 g++ cv_test.cpp -o cv_test -pthread
编译并执行会产生以下输出:
Woken up
Notifying
Woken up
Woken up
Woken up
Woken up
Woken up
Woken up
Exiting
Woken up
Counter: 0
注意我只调用了一次notify_one
,但是线程被连续唤醒直到谓词returns为假。无论计数器初始化为什么,线程都会被唤醒,直到它变为 0(这是谓词)。
即使在执行开始时,线程也会被唤醒一次,好像要“检查”谓词 returns 是否为假。因此,如果我将计数器初始化为正值:int counter = 3; // (1)
,虚假唤醒似乎甚至在调用第一个 notify_one
之前就“确保”谓词 returns 为假。
我的问题是,这真的是一项功能吗?可以依赖吗?是否有任何关于此行为的文档?
PS。我知道这个工作线程可以通过在等待 condition_variable 之前简单地检查计数器(读取:工作队列长度)来修复,但是这种虚假唤醒的可预测行为让我很感兴趣。
我在发布这个问题后立即意识到 condition_variable::wait
的重载(如 here 所述)等同于:
while (!pred()) {
wait(lock);
}
我原以为它等同于 do while
。所以这里真的没有虚假的 wake-ups 。只是它根本没有等到谓词返回 false。
我 运行 喜欢这种有趣的虚假唤醒行为。考虑这个简单的演示代码:
#include <iostream>
#include <chrono>
#include <thread>
#include <condition_variable>
#include <mutex>
using namespace std; // I know
using namespace std::chrono;
using namespace std::chrono_literals;
mutex mtx; // used for cv and synchronized access to counter
condition_variable cv;
int counter = 0; // (1)
bool keep_running = true; // flag for signaling an exit condition
int main()
{
// thread that decrements counter every time it is woken up and the counter is > 0
thread t([&] {
while (keep_running)
{
unique_lock<mutex> lock(mtx);
cv.wait(lock, [&] {
cout << "Woken up" << endl;
return !keep_running || counter > 0;
});
if (!keep_running) { // check for exit condition
break;
}
--counter;
}
});
this_thread::sleep_for(1s); // some work
unique_lock<mutex> lock(mtx);
counter = 5; // set the counter
cout << "Notifying" << endl;
lock.unlock();
cv.notify_one(); // wake the thread up
this_thread::sleep_for(1s); // some more work
cout << "Exiting" << endl;
lock.lock();
keep_running = false; // ask the thread to exit
lock.unlock();
cv.notify_one(); // wake up one last time
t.join(); // join and exit
cout << "Counter: " << counter << endl;
}
使用 g++ cv_test.cpp -o cv_test -pthread
编译并执行会产生以下输出:
Woken up
Notifying
Woken up
Woken up
Woken up
Woken up
Woken up
Woken up
Exiting
Woken up
Counter: 0
注意我只调用了一次notify_one
,但是线程被连续唤醒直到谓词returns为假。无论计数器初始化为什么,线程都会被唤醒,直到它变为 0(这是谓词)。
即使在执行开始时,线程也会被唤醒一次,好像要“检查”谓词 returns 是否为假。因此,如果我将计数器初始化为正值:int counter = 3; // (1)
,虚假唤醒似乎甚至在调用第一个 notify_one
之前就“确保”谓词 returns 为假。
我的问题是,这真的是一项功能吗?可以依赖吗?是否有任何关于此行为的文档?
PS。我知道这个工作线程可以通过在等待 condition_variable 之前简单地检查计数器(读取:工作队列长度)来修复,但是这种虚假唤醒的可预测行为让我很感兴趣。
我在发布这个问题后立即意识到 condition_variable::wait
的重载(如 here 所述)等同于:
while (!pred()) {
wait(lock);
}
我原以为它等同于 do while
。所以这里真的没有虚假的 wake-ups 。只是它根本没有等到谓词返回 false。