2014-01-29 76 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