![](/img/trans.png)
[英]Memory location of a std::condition_variable can cause futex error
[英]std::condition_variable memory writes visibility
std::condition_variable::notify_one()
或std::condition_variable::notify_all()
保證在調用之前當前線程中的非原子內存寫入在通知線程中可見?
其他線程做:
{
std::unique_lock lock(mutex);
cv.wait(lock, []() { return values[threadIndex] != 0; });
// May a thread here see a zero value and therefore start to wait again?
}
主線程執行:
fillData(values); // All values are zero and all threads wait() before calling this.
cv.notify_all(); // Do need some memory fence or lock before this
// to ensure that new non-zero values will be visible
// in other threads immediately after waking up?
notify_all() 不存儲一些原子值從而強制執行內存排序嗎? 我沒有澄清。
UPD :根據 Superlokkus 的回答和這里的回答:我們必須獲取一個鎖以確保其他線程中的內存寫入可見性(內存傳播),否則在我的情況下線程可能會讀取零值。
我也錯過了這里關於 condition_variable 的引用,它專門回答了我的問題。 在修改必須立即可見的情況下,甚至必須在鎖下修改原子變量。
即使共享變量是原子的,也必須在互斥鎖下進行修改,才能將修改正確發布到等待線程。
我猜您正在混淆所謂的原子值的內存排序和基於鎖的經典同步機制。
當你有一個在線程之間共享的數據時,比如說一個int
,一個線程不能簡單地讀取它,而另一個線程可能同時寫入它。 否則我們就會有數據競賽。
為了長時間解決這個問題,我們使用了經典的基於鎖的同步:線程至少共享一個互斥鎖和int
。 要讀取或寫入任何線程必須先持有鎖,這意味着它們在互斥鎖上等待。 互斥體被構建,以便它們可以同時發生。 如果一個線程贏得互斥鎖,它可以更改或讀取int
然后應該解鎖它,這樣其他人也可以讀/寫。 使用像您使用的條件變量只是為了使“讀取器等待寫入器更改值”的模式更有效,它們會被 cv 喚醒,而不是定期等待鎖定、讀取和解鎖,這會被稱為忙等待。
因此,因為在等待互斥鎖之后或在您的情況下,正確地(仍然需要互斥鎖)等待條件變量后,您將鎖定保持在任何位置,您可以更改int
。 讀者將在作者能夠寫出新值后讀取新值,而不是舊值。 更新:但是,如果必須添加一件事,這也可能是混淆的原因:條件變量受所謂的虛假喚醒的影響。 這意味着即使您寫入沒有通知任何線程,讀取線程可能仍會喚醒,互斥鎖被鎖定。 因此,您必須檢查您的作者是否真的喚醒了您,這通常是由作者通過更改另一個數據來通知這一點來完成的,或者通過使用您已經想共享的相同數據是否合適。 std::condition_variable::wait
的 lambda 參數重載只是為了使檢查和返回睡眠代碼看起來更漂亮一些。 根據你現在的問題,我不知道你是否想在這份工作中使用你的values
。
但是,“主”線程的代碼片段不正確或不完整:您沒有在互斥鎖上進行同步以更改values
。 您必須為此持有鎖,但可以在沒有鎖的情況下進行通知。
std::unique_lock lock(mutex);
fillData(values);
lock.unlock();
cv.notify_all();
但是這些基於互斥鎖的模式有一些缺點並且速度很慢,一次只有一個線程可以做某事。 這就是所謂的原子,就像std::atomic<int>
發揮作用。 它們可以同時被多個線程同時寫入和讀取,而無需互斥鎖。 內存排序只是一個需要考慮的事情,並且對於您以有意義的方式使用其中幾個或者您不需要“寫入后,我永遠不會看到舊值”保證的情況進行優化。 但是,使用它的默認內存排序memory_order_seq_cst
您也可以。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.