在以下选项中,使用条件变量时,是否有任何正确的方法来处理虚假唤醒?
1)使用 bool 值将wait(unique_lock_ul)
放入无限while
循环中
unique_lock<mutex> ul(m);
while(!full)
cv.wait(ul);
2)与if相同
unique_lock<mutex> ul(m);
if(!full)
cv.wait(ul);
3)将条件放入
wait()
中,例如通过使用lambda函数unique_lock<mutex> ul(m);
cv.wait(ul, [&](){return !full;});
如果所有这些都不正确,那么如何轻松地处理虚假唤醒呢?
我对C++中的条件变量比较陌生,并且不确定所读取的某些代码是否处理了虚假唤醒的情况。
最佳答案
简短的答案是,您的代码可能是正确的,也可能是错误的;您没有确切显示full
的操作方式。
C++代码的各个位永远都不是线程安全的。线程安全是代码的关系属性。如果代码的两个位永远不会引起争用条件,则它们彼此之间可以是线程安全的。
但是,只有一点点代码永远都不是线程安全的。说某事是线程安全的,就像说某事是“相同的高度”一样。
“monkey see monkey do”条件变量模式是这样的:
template<class T>
class cv_bundle {
std::mutex m;
T payload;
std::condition_variable cv;
public:
explicit cv_bundle( T in ):payload(std::move(in)) {}
template<class Test, class Extract>
auto wait( Test&& test, Extract&& extract ) {
std::unique_lock<std::mutex> l(m);
cv.wait( l, [&]{ return test(payload); } );
return extract(payload);
}
template<class Setter>
void load( Setter&& setter, bool only_one = true ) {
std::unique_lock<std::mutex> l(m);
bool is_set = setter( payload );
if (!is_set) return; // nothing to notify
if (only_one)
cv.notify_one();
else
cv.notify_all();
}
};
test
接受T& payload
并在有消耗的东西时返回true(即,唤醒不是虚假的)。extract
接受T& payload
并从中返回您想要的任何信息。通常应重置有效负载。setter
以T& payload
将返回test
的方式修改true
。如果这样做,则返回true
。如果选择不这样做,则返回false
。在互斥锁中访问
T payload
的所有这三个函数都被调用。现在,您可以对此进行生成,但是很难做到这一点。例如,不要假定原子有效载荷意味着您不必锁定互斥体。
当我将这三件事 bundle 在一起时,您可以将单个互斥锁用于一堆条件变量,或者将互斥锁用于不仅仅是条件变量。有效载荷可以是 bool 值,计数器,数据 vector 或其他外来物。通常,它必须始终受互斥锁保护。如果它是原子的,则在修改值和通知之间的打开时间间隔内的某个时间点,互斥锁必须锁定,否则您可能会丢失通知。
手动循环控制而不是传递lambda是一种修改,但是描述哪种手动循环是合法的以及哪些是竞争条件是一个复杂的问题。
实际上,除非我有充分的理由,否则我避免使用这种使用条件变量的猴子(参见monkey-do的“cargo cult”)样式。然后,我被迫阅读C++内存和线程模型,这并没有使我费劲,这意味着我的代码很可能是不正确的。
请注意,如果传入的任何lambda都回叫到
cv_bundle
,则我显示的代码将不再有效。关于c++ - 通常,处理虚假唤醒的正确方法是什么?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/52192203/