据我所知,只有带有谓词的condition_variable.wait_for
(因为在内部进行双重检查)才能避免被虚假唤醒所阻止,但没有谓词的版本(如果不使用时间)则不能解除阻塞。
但是,如果我想做一些事情时,只有cv_status::timeout
发生了,并做了其他事情的notify_XXX
?因为带有谓词的condition_variable.wait_for
只返回bool
,所以它无法判断是否被notify_XXX
或cv_status::timeout
解除阻塞;尽管没有谓词的condition_variable.wait_for
返回cv_status::timeout
,但它无法判断是否被虚假唤醒或notify_XXX
解除阻塞。
发布于 2022-06-14 13:55:57
条件变量最好作为三重变量使用。cv,互斥体和有效载荷。
如果没有有效载荷(隐式或显式),就无法确定唤醒是否是假的。
谓词版本使检查有效负载变得容易,但在一些复杂的情况下,如果不使用lambda,检查有效负载可能会更容易。因此,提供了另一个API。
修改有效载荷后,条件变量所操作的互斥对象在发送信号之前必须处于锁定状态。(例如,您可以使用互斥锁来保护有效负载;或者您可以修改有效载荷,原子化,然后锁定和解锁互斥体,然后发送信号)。否则,可能会出现假唤醒(丢失信号)的相反情况。
所有这些都很难纠正,而且很容易不小心出错。
如果您想要编写新的并发代码(特别是使用低级原语),您必须了解足够多的C++内存模型,并学习如何证明算法是正确的。因为它是一种很难编写代码并将其正确性建立在“行得通”之上的方法。
如果没有额外的数据,您已经正确地识别出无法解决这个问题。您需要添加额外的数据,并使用它来确定唤醒是假的还是真实的。那是故意的。
C++本可以将额外的数据添加到条件变量中,但是即使您没有使用它,它也会让您为此付出代价。条件变量是一个低级原语,它允许您尽可能地编写尽可能接近最优的代码,事实上,将它封装在类中可能会使某些人感到困惑。
而且有很多有效载荷。如果您有一个计数信号量,其中发送信号的数量与接收信号的数量相匹配,那么您的有效负载将是一个整数。如果您有一个闩锁或门,一旦打开,每个人都可以自由地通过它,你的有效载荷将是一个bool。
struct gate {
void wait_on_gate() const {
auto l = lock();
cv.wait( l, [&]{ return !closed; } );
}
// false iff it times out
template<class Time>
bool wait_on_gate_until(Time time) const {
auto l = lock();
return cv.wait_until( l, time, [&]{ return !closed; } );
}
// false iff it times out
template<class Duration>
bool wait_on_gate_for(Duration d) const {
auto l = lock();
return cv.wait_for( l, d, [&]{ return !closed; } );
}
// Once you call this, nobody waits
void open_gate() {
auto l = lock();
closed = false;
cv.notify_all();
}
private:
mutable std::mutex m;
std::condition_variable cv;
bool closed = true;
};
现在你会注意到我使用的是lambda版本。
我们可以重构到非lambda版本:
void wait_on_gate() const {
auto l = lock();
while(closed)
cv.wait( l );
}
template<class Time>
void wait_on_gate_until(Time time) const {
auto l = lock();
while(closed) {
if (cv.wait_until(l, time) == std::cv_status::timeout)
return !closed;
}
return true;
}
这更复杂,而且作用完全一样。(假设我没有打字)。
唯一的区别是,你可以做一些花哨的事情,而这些事情可能不适合在灯笼里。例如,你可以选择说“嗯,这是假的,但当我醒着时,我会去其他地方做一些簿记,然后回来”。
https://stackoverflow.com/questions/72603420
复制相似问题