在下面的选项中,有没有正确的方法来处理使用条件变量时的虚假唤醒?
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)在wait()
中放置一个条件,例如使用lambda函数
unique_lock<mutex> ul(m);
cv.wait(ul, [&](){return !full;});
如果这一切都不正确,那么如何轻松应对虚假唤醒呢?
我对C ++中的条件变量很新,我不确定我读过的一些代码是否与虚假唤醒的情况有关。
简短的回答是,您的代码可能是正确的还是错误的;你没有准确显示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
。
所有3个都在互斥锁中访问T payload
。
现在,您可以为此生成变体,但这样做很难做到。例如,不要假设原子有效负载意味着您不必锁定互斥锁。
虽然我将这3个内容捆绑在一起,但您可以使用单个互斥锁来处理一堆条件变量,或者将互斥锁用于多个条件变量。有效载荷可以是布尔,计数器,数据向量或更外星的东西;通常,它必须始终受互斥锁保护。如果它是原子的,在被修改的值和通知之间的某个时间点内,必须锁定互斥锁,否则您可能会丢失通知。
手动循环控制而不是传入lambda是一种修改,但描述什么样的手动循环是合法的,哪些是竞争条件是一个复杂的问题。
实际上,除非我有充分的理由,否则我会避免让这种猴子看到猴子做“货物崇拜”的条件变量使用方式。然后我被迫阅读C ++内存和线程模型,这不是我的一天,这意味着我的代码很可能不会是正确的。
请注意,如果任何传入的lambda传入并回调到cv_bundle
,我显示的代码将不再有效。
处理虚假唤醒时可以使用1或3路(假设full
修改受同一个互斥锁保护),除非你得到谓词条件错误,它应该是:
unique_lock<mutex> ul(m);
cv.wait(ul, [&](){return full;});
使此代码等于变体1。
变体2并不好,因为与其他2种情况不同,虚假的唤醒等待条件不会被重新检查。