11

各种平台都允许虚假唤醒。为了解决这个问题,我们写了下面的循环机制:

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()处理虚假唤醒?

4

4 回答 4

9

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

它没有。

此功能通常用于以下情况:如果您虚假地醒来,您无论如何都想做一些其他工作。如果你不虚假地醒来,你想在时间duration过去时强制“虚假”唤醒。这意味着它通常不会在您显示的循环中使用,这正是您所说的原因。即超时和虚假唤醒被同等对待。

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

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;
于 2015-08-07T14:32:26.993 回答
3

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

30.5 条件变量[thread.condition]

条件变量提供用于阻塞线程的同步原语,直到某个其他线程通知满足某些条件或直到达到系统时间。

...

10 注意:用户有责任确保等待线程在遇到虚假唤醒时不会错误地认为线程已完成。

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

于 2015-08-07T09:43:33.897 回答
1

自己检查假电话

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

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

在这种情况下,当main()线程没有设置terminate并且cv.wait_until()没有超时时,这意味着没有达到超时但是(由系统)通知 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
于 2018-06-28T13:20:41.277 回答
1

    常量自动持续时间 = Returns_10_seconds();
    while(cv.wait_for(lock, duration) == std::cv_status::timeout);

这绝对是一件错误的事情,因此讨论如何在虚假唤醒的情况下修复它是没有意义的,因为即使对于普通唤醒的情况它也被破坏了,因为从返回后没有重新检查等待条件这段等待。

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

即使在编辑之后,答案也保持不变:您无法真正处理“虚假唤醒”,因为您无法真正说出唤醒的原因 - 由于condition_variable::notifyXXX在超时已过期。

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

你真正要做的就是以一种特殊的方式对待,不是在超时之前醒来,而是因为超时而醒来。这完全取决于首先超时的原因,即取决于应用程序/问题域的细节。

[1] 如果条件变量上的等待被信号中断,则在执行信号处理程序后,允许线程继续等待或返回

于 2015-08-07T10:15:52.120 回答