2014-01-29 46 views
3

所以我刚刚发现,如果您没有在C++ 11中保存锁定,则发出一个条件变量的信号是合法的。这似乎打开大门,一些讨厌的竞争条件:无条件锁定的条件变量上的信号

std::mutex m_mutex; 
std::condition_variable m_cv; 

T1: 
    std::unique_lock<std::mutex> lock(m_mutex); 
    m_cv.wait(lock, []{ return !is_empty(); }); 

T2: 
    generate_data(); 
    m_cv.notify(); 

能够保证所有的T1将永远在我们检查is_empty(这种情况最终会)第(它返回true),然后让由T2抢占其创建一些数据并在实际等待之前发送条件变量的信号?

如果这是保证工作(我猜是这样,否则它看起来像一个故意的糟糕的API设计),这是如何实现的说linux和stdlibc++?似乎我们需要另一个锁来避免这种情况。

+0

是的,这是一个竞争条件的精确配方,就像http://stackoverflow.com/q/20982270/412080 –

回答

4

检查谓词和等待不在std::condition_variable::wait中原子执行(解锁和睡眠自动执行)。如果在此线程持有互斥锁时,另一个线程可能更改谓词的值,则谓词检查和入睡之间可能发生通知,并且可能会丢失。

在你的榜样,如果generate_data()T2可以改变的is_empty()结果未持有m_mutex,有可能为一个通知T1检查is_empty()和睡在m_cv之间发生。在谓词和通知的变化之间随时保持互斥体足以保证谓词检查和wait在另一个线程中的调用。这可能是这样的:

{ 
    std::lock_guard<std::mutex> lk(m_mutex); 
    generate_data(); 
} 
m_cv.notify(); 

甚至

generate_data(); 
std::lock_guard<std::mutex>(m_mutex); // Lock the mutex and drop it immediately 
m_cv.notify(); 
+0

我对这里的表述略显sl and,并假定每个人都会熟悉那些锁定数据发布和通知的一般习惯用法。但你说得对,只是锁定实际的发布就足够了,我认为在某些情况下是一个很好的理由(我怀疑它是否对所显示的代码有所影响,无论通知是在锁还是在外)以允许此场景。 – Voo

+0

@Voo通知内部与在锁之外并不影响正确性,如果等待线程(或者'notify_all'情况下的多个线程)仅在立即阻塞互斥体时醒来,那么在锁内通知浪费了一点性能。 – Casey

+0

@Casey:为什么你的第一个解决方案不起作用?即在进行通知()之前进行锁定?如果T2获得锁定,T1将不会进入等待状态,并且当它发生时,谓词为真,因此它不会等待,这是预期的行为。 – user1715122

3

它并不保证 - 如果你不想错过的信号,那么你必须通知之前互斥锁。有些应用程序可能对丢失信号不可知。

从人pthread_signal:

的调用pthread_cond_signal()或调用pthread_cond_broadcast()功能可以通过线程调用它当前是否拥有该线程调用pthread_cond_wait()的或那么pthread_cond_timedwait()已经与条件相关联的互斥在等待期间变化;然而,如果需要可预测的调度行为,那么调用pthread_cond_signal()或pthread_cond_broadcast()的线程会锁定该互斥锁。

+0

锁定信令之前的互斥不足以保证信号不会被遗漏;有必要确保在锁定期间条件(相关谓词)的值不能改变。 – Casey

+0

什么是基于原子计数器的谓词?这不是说现在我们在修改原子变量之前必须先锁定,从而破坏它们原子的整体目的吗? – user1715122