簡體   English   中英

了解原子變量和操作

[英]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:以下陳述正確嗎?

  • * 3行確保如果在釋放順序下的某些操作(在其他線程中,如* 2)可見,則在釋放順序以上的每個操作(例如* 1)也將可見。
  • 這意味着斷言* 5不會失敗(默認值為false)。
  • 但這不能保證即使物理上(在處理器中)* 2發生在* 3之前,也可以通過在上面(在不同線程中運行)進行剪切而看到-函數read_y_then_x()仍然可以讀取舊值。 唯一可以保證的是,如果y為true,則x也將為true。

#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個

在考慮了#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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM