![](/img/trans.png)
[英]Is there any effect on the operations with the variables independent of consume atomic-load?
[英]Understanding atomic variables and operations
我一遍又一遍地了解了boost和std(c ++ 11)的原子類型和操作,但是我仍然不確定我是否理解正確(在某些情況下我根本不理解)。 因此,我對此有一些疑問。
我用來學習的資料來源:
考慮以下代碼段:
atomic<bool> x,y;
void write_x_then_y()
{
x.store(true, memory_order_relaxed);
y.store(true, memory_order_release);
}
#1:是否等效於下一個?
atomic<bool> x,y;
void write_x_then_y()
{
x.store(true, memory_order_relaxed);
atomic_thread_fence(memory_order_release); // *1
y.store(true, memory_order_relaxed); // *2
}
#2:以下陳述正確嗎?
第* 1行保證,當在該行下完成的操作(例如* 2)可見(對於使用Acquisition的其他線程)時,* 1以上的代碼也將可見(帶有新值)。
接下來的片段擴展了上面的內容:
void read_y_then_x()
{
if(y.load(memory_order_acquire))
{
assert(x.load(memory_order_relaxed));
}
}
#3:是否等效於下一個?
void read_y_then_x()
{
atomic_thread_fence(memory_order_acquire); // *3
if(y.load(memory_order_relaxed)) // *4
{
assert(x.load(memory_order_relaxed)); // *5
}
}
#4:以下陳述正確嗎?
#5:遞增(加1的操作)原子整數可以被memory_order_relaxed並且沒有數據丟失。 唯一的問題是結果可見性的順序和時間。
根據提升,以下是工作參考計數器:
#include <boost/intrusive_ptr.hpp>
#include <boost/atomic.hpp>
class X {
public:
typedef boost::intrusive_ptr<X> pointer;
X() : refcount_(0) {}
private:
mutable boost::atomic<int> refcount_;
friend void intrusive_ptr_add_ref(const X * x)
{
x->refcount_.fetch_add(1, boost::memory_order_relaxed);
}
friend void intrusive_ptr_release(const X * x)
{
if (x->refcount_.fetch_sub(1, boost::memory_order_release) == 1) {
boost::atomic_thread_fence(boost::memory_order_acquire);
delete x;
}
}
};
#6為什么要減少使用的memory_order_release? 它是如何工作的(在上下文中)? 如果我早先寫的是真的,是什么使返回值是最新的,特別是當我們使用獲取AFTER讀數而不是在讀前/讀中?
#7為什么參考計數器達到零后會有獲取指令? 我們剛剛讀到計數器為零,並且沒有使用其他原子變量(指針本身未標記/使用)。
1:否。釋放圍欄與所有獲取操作和圍欄同步。 如果存在在第三線程中操縱的第三atomic<bool> z
,則籬笆也將與該第三線程同步,這是不必要的。 話雖如此,它們在x86上的作用相同,但這是因為x86具有非常強的同步性。 1000個核心系統上使用的體系結構往往較弱。
2:是的,這是正確的。 柵欄確保您看到之后的所有內容,也看到之前的所有內容。
3:通常,它們是不同的,但實際上它們是相同的。 允許編譯器對不同變量的兩個寬松操作進行重新排序,但不得引入虛假操作。 如果編譯器有某種方式可以確定需要讀取x,則可以在讀取y之前這樣做。 在您的特定情況下,這對於編譯器來說非常困難,但是在許多類似的情況下,這種重新排序是公平的。
4:所有這些都是對的。 原子操作保證一致性。 它們並不總是保證事情按照您想要的順序發生,它們只是防止破壞您的算法的病理順序。
5:正確 輕松的操作確實是原子的。 他們只是不同步任何額外的內存
6:對於任何給定的原子對象M
,C ++保證對M
進行操作的“正式”順序。 您不會像C ++那樣看到M
的“最新”值,並且處理器保證所有線程將看到M
一系列一致的值。 如果有兩個線程將refcount遞增,然后遞減,則沒有保證人會將其遞減為0,但有一個保證人保證其中一個會看到將其遞減為0。這兩種方法都無法實現看到它們遞減了2-> 1和2-> 1,但是以某種方式引用計數將它們組合為0。一個線程將始終看到2-> 1,而另一個線程將看到1-> 0。
請記住,內存順序更多地是關於在原子周圍同步內存。 無論您使用什么存儲順序,原子都將得到正確處理。
7:這比較棘手 7的簡短版本是遞減是釋放順序,因為某些線程必須運行x的析構函數,並且我們要確保它能看到在所有線程上對x進行的所有操作。 在析構函數上使用發布順序可以滿足此需求,因為您可以證明它可以正常工作。 負責刪除x的人必須先獲取所有更改(使用圍欄確保刪除器中的原子不會向上漂移)。 在線程釋放自己的引用的所有情況下,很明顯,在調用刪除程序之前,所有線程都會有一個釋放順序遞減。 如果一個線程增加了引用計數,而另一個線程減少了引用計數,則可以證明唯一有效的方法是線程彼此同步,以便析構函數可以看到兩個線程的結果。 同步失敗無論如何都會造成爭用情況,因此用戶必須正確處理。
在考慮了#1之后,我已經確信[atomics.fences]
此論點§29.8.3
不等同於[atomics.fences]
:
如果存在原子操作X,使得釋放柵欄A與原子操作B同步,原子操作B對原子對象M執行獲取操作,使得A在X之前排序,X修改M,並且B讀取X寫入的值或一個值如果是釋放操作,則假設的釋放順序X中任何由副作用產生的結果都會出現。
本段說,釋放防護只能與獲取操作同步。 但是釋放操作可以與消耗操作同步。
帶有獲取圍欄的void read_y_then_x()將圍欄放置在錯誤的位置。 它應該放置在兩個原子載荷之間。 從本質上講,獲取圍欄會使圍欄上方的所有負載都像獲取負載一樣起作用,除了之前執行該圍欄之前無法確定發生的情況。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.