簡體   English   中英

並發讀取非原子變量

[英]Concurrent reads on non-atomic variable

我在嘗試實現共享指針時遇到了這個問題。 讓我們關注托管數據指針。 它的生命周期可以分為三個階段:

  1. 沒有並發訪問的構造。
  2. 並發讀取(無寫入)。
  3. 沒有並發訪問的破壞。 這是通過引用計數來保證的。

我的問題是,鑒於這種情況,指針是否必須是原子的? 我認為這等效於:如果指針不是原子的,第二階段會導致未定義的行為嗎? 理想情況下,我希望聽到從理論(語言律師)角度和實踐角度進行討論的答案。 例如,如果不是原子的,則階段2在理論上可能是未定義的行為,但在實際平台上實際上是可以的。 為了實現共享指針,如果non-atomic可以,托管的指針可以是unique_ptr<T> ,否則必須是atomic<T*>

更新

我找到了標准文字(第1.10頁p21):

如果程序的執行在不同線程中包含兩個沖突的動作,則其中至少有一個不是原子的,並且在兩個線程之前都沒有發生,因此該程序的執行將引起數據競爭。 任何此類數據爭用都會導致未定義的行為。

我猜並發讀取不會歸類為沖突操作。 可以肯定有人找到一些標准的文字嗎?

規則是,如果一個以上的線程同時訪問同一對象,並且其中至少有一個線程正在修改數據,則您將發生數據爭用,並且程序的行為是不確定的。 如果沒有人在修改對象,則並發訪問不會有問題。

自己找到答案。 引用自第一段, C ++並發性中的 5.1.2節:

[...]如果兩個線程都沒有更新內存位置,就可以了; 只讀數據不需要保護或同步。 如果任何一個線程正在修改數據,則存在爭用條件的可能性,如第3章所述。

由於[intro.multithread]中存在沖突的評估定義,因此並發讀取任何變量(無論是否為原子)都不構成數據爭用:

如果兩個表達式評估之一修改一個內存位置,而另一個表達式訪問或修改相同的內存位置,則這兩個表達式評估會發生沖突

最近,此詞已移至[intro.races] ,措辭非常微妙

如果兩個表達式求值之一修改一個內存位置,而另一個表達式讀取或修改了相同的內存位置,則這兩個表達式求值會沖突

訪問讀取的更改發生在n4296和n4431草案之間。 多線程部分的拆分在n4582和n4604之間進行。

因此,我想您正在談論持有計數器和指向所擁有數據的指針的結構:

template<class ValueType>
struct shared_counter{
  std::atomic<int> count=0;
  const std::unique_ptr<ValueType> ptr;
  //Your question is: Does ptr should be atomic?
  //... This is a dumb implementation, only focusing on the subject.
  };

實際上,ptr不必是原子的,因為如果適當地實現了引用計數,則對ptr所有訪問都將在對shared_counter進行說明之前進行排序。

為確保在shared_ptr的析構函數內,計數器通過具有獲取釋放內存順序的讀-修改-寫操作遞減:

template<class ValueType>
struct shared_ptr{
   shared_counter<ValueType>* counted_ptr;
   //...
   void reset(){
     if (counted_ptr->count.fetch_sub(1,std::memory_order_acq_rel) == 1)
       counter_ptr->~shared_counter<ValueType>();
     counter_ptr=nullptr;
     }
   };

由於此內存順序,如果在線程A中獲取的count值為1,這意味着在所有其他線程中,指向相同shared_counter 其他 shared_ptrs將不再訪問此shared_counter 內存順序確保了在其他線程中對該共享計數器執行的訪問將在獲取線程A中的值1之前發生(在其他線程中釋放->在將調用析構函數的線程中獲取)。

因此,無需將ptr設為原子,因為計數器的遞減會導致足夠的排序。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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