condition_variable::wait_for() 如何处理虚假唤醒?

How does condition_variable::wait_for() deal with spurious wakeups?

Spurious wakup 被各种平台所允许。为了解决这个问题,我们在下面编写循环机制:

while(ContinueWaiting())
  cv.wait(lock);   // cv is a `std::conditional_variable` object

conditional_variable::wait_until()也是可以理解的。
但是看看下面的例子:

const auto duration = Returns_10_seconds();
while(!Predicate())
  cv.wait_for(lock, duration);

想象一下,虚假唤醒发生在 1 秒。尚未达到超时。
它会再等 10 秒吗?这将导致无限循环,我相信这不应该发生。从源代码,内部 wait_for() 调用 wait_until().

我想了解,wait_for() 如何处理虚假唤醒?

以下是标准对虚假唤醒的规定:

30.5 Condition variables [thread.condition]

Condition variables provide synchronization primitives used to block a thread until notified by some other thread that some condition is met or until a system time is reached.

...

10 Note: It is the user’s responsibility to ensure that waiting threads do not erroneously assume that the thread has finished if they experience spurious wakeups.

从措辞上看,处理虚假唤醒的责任似乎在用户身上。

    const auto duration = Returns_10_seconds();
    while(cv.wait_for(lock, duration) == std::cv_status::timeout);

这绝对是错误的做法,因此讨论如何针对虚假唤醒的情况修复它是没有意义的,因为它甚至对于普通唤醒的情况也是如此,因为等待条件不是重新- 在等待 returning 之后进行检查。

const auto duration = Returns_10_seconds();
while(!Predicate())
  cv.wait_for(lock, duration);

即使在编辑之后,答案仍然保持不变:你无法真正处理 "spurious wakeups",因为你无法真正说出唤醒的原因 - 它很可能是完全合法的唤醒,因为在超时到期前调用 condition_variable::notifyXXX

首先,请注意,您无法真正区分由调用 condition_variable::notifyXXX 引起的唤醒和由 POSIX 信号 [1] 等引起的唤醒。 其次,即使不关心 POSIX 信号,等待线程仍然必须重新检查条件,因为条件变量可能在发出信号和等待线程 returns 之间发生变化从条件等待。

你真正需要做的是特殊对待,不是在超时前醒来,而是由于超时醒来。这完全取决于首先超时的原因,即 application/problem 域的具体情况。

[1] 如果等待条件变量被信号中断,则在执行信号处理程序后允许线程恢复等待或 return

I want to understand, how does wait_for() deals with spurious wakeups?

没有。

此功能通常用于以下情况:如果您虚假地醒来,但无论如何都想做一些其他工作。如果你没有虚假地醒来,你想在 duration 过去之前强制 "spurious" 醒来。这意味着它通常不会像您所展示的那样在循环中使用,这正是您陈述的原因。 IE。超时和虚假唤醒的处理方式相同

现在您可能想知道谓词版本的作用是什么,因为它暗示了一个循环?

template <class Rep, class Period, class Predicate>
bool
wait_for(unique_lock<mutex>& lock, const chrono::duration<Rep, Period>& rel_time,
         Predicate pred);

这被指定为具有与以下相同的效果:

return wait_until(lock, chrono::steady_clock::now() + rel_time, std::move(pred));

wait_until 变体 区分虚假唤醒和超时。它通过这样的循环来实现:

while (!pred())
    if (wait_until(lock, abs_time) == cv_status::timeout)
        return pred();
return true;

自己查一下虚假电话

cv.wait_for() 不处理虚假唤醒。

您可以通过引用将布尔标志传递给线程来处理虚假唤醒,并在 cv.wait_until() 不是超时时检查它。

在这种情况下main()线程没有设置terminatecv.wait_until()有no_timeout,这意味着没有达到超时但通知cv(由系统) ,所以这是一个虚假电话。

bool terminate = false;
std::unique_lock<std::mutex> lock(mutex);
const auto time_point = std::chrono::system_clock::now() + std::chrono::seconds(10);
const std::cv_status status = cv.wait_until(lock, time_point);
if (status == std::cv_status::timeout) {
    std::cout << "timeout" << std::endl;
}
else { // no_timeout
    if (terminate) {
        std::cout << "terminate" << std::endl;
        break;
    }
    else {
        std::cout << "spurious" << std::endl;
    }
}

完整代码

#include <iostream>
#include <mutex>
#include <condition_variable>
#include <chrono>

class Thread {
private:
    std::condition_variable cv;
    std::mutex mutex;
    bool terminate = false;
    std::thread thread;
public:
    Thread() {
        thread = std::thread([&]() {
            while (true) {
                std::unique_lock<std::mutex> lock(mutex);
                const auto time_point = std::chrono::system_clock::now() + std::chrono::seconds(10);
                const std::cv_status status = cv.wait_until(lock, time_point);
                if (status == std::cv_status::timeout) {
                    std::cout << "timeout" << std::endl;
                }
                else { // no_timeout
                    if (terminate) {
                        std::cout << "terminate" << std::endl;
                        break;
                    }
                    else {
                        std::cout << "spurious" << std::endl;
                    }
                }
            }
        });
    }
    virtual ~Thread() {
        {
            std::lock_guard<std::mutex> lock(mutex);
            terminate = true;
        }
        cv.notify_all();
        if (thread.joinable()) {
            thread.join();
        }
    }
};

void main() {
    {
        Thread thread;
        std::this_thread::sleep_for(std::chrono::seconds(15));
    }
    std::cin.get();
}

当 main() sleep_for()

时的完整代码结果
5 seconds:
terminate

15 seconds:
timeout
terminate

15 seconds in Visual Studio Debug mode:
spurious
terminate