在以下选项中,使用条件变量时,是否有任何正确的方法来处理虚假唤醒?
1)使用布尔值将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)例如,通过使用lambda函数,在wait()
中放入条件
unique_lock<mutex> ul(m);
cv.wait(ul, [&](){return !full;});
如果这都不正确,那么如何轻松处理虚假唤醒?
我对C ++中的条件变量还很陌生,我不确定所阅读的某些代码是否可以处理虚假唤醒。
答案 0 :(得分:2)
简短的答案是,您的代码可能是正确的还是错误的;您没有确切显示full
的操作方式。
C ++代码的各个位永远都不是线程安全的。线程安全是代码的关系属性;如果两个代码永远不会引起争用条件,则它们彼此之间是线程安全的。
但是只有一点点代码永远不会线程安全;说某事是线程安全的,就像说某事是“相同的高度”。
“猴子见猴子做”条件变量模式是这样的:
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 ) {
bool is_set = false;
{
std::unique_lock<std::mutex> l(m);
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
内调用所有3个。
现在,您可以对此进行生成,但是很难做到这一点。例如,不要假设原子有效载荷意味着您不必锁定互斥体。
虽然我将这三样东西捆绑在一起,但是您可以将单个互斥锁用于一堆条件变量,或者将互斥锁用于不仅仅是条件变量。有效载荷可以是布尔值,计数器,数据向量或其他异物。通常,它必须始终受互斥锁保护。如果它是原子的,则在修改值和通知之间的打开时间间隔内,必须锁定互斥锁,否则您可能会丢失通知。
手动循环控制而不是传递lambda是一种修改,但是描述哪种手动循环是合法的以及哪些是竞争条件是一个复杂的问题。
实际上,除非我有非常充分的理由,否则我避免避免使用这种使用条件变量的“猴子崇拜”样式。然后,我被迫阅读C ++内存和线程模型,这并不是我的工作,这意味着我的代码很可能是不正确的。
请注意,如果传入的任何lambda都回拨到cv_bundle
,则我显示的代码将不再有效。
答案 1 :(得分:2)
除了谓词条件错误外,用1或3种方法都可以处理虚假唤醒(假设full
修改受同一个互斥锁保护),这应该是:
unique_lock<mutex> ul(m);
cv.wait(ul, [&](){return full;});
使此代码等于变量1。
与其他2种情况不同,变体2效果不佳,因为在伪造唤醒等待条件下不会重新检查。