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。