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()
线程没有设置terminate
而cv.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
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()
线程没有设置terminate
而cv.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