簡體   English   中英

為什么std :: atomic initialisation沒有原子釋放,所以其他線程可以看到初始值?

[英]Why doesn't std::atomic initialisation do atomic release so other threads can see the initialised value?

提議的boost :: concurrent_unordered_map的線程清理過程中出現了一些非常奇怪的東西,並在這篇博客文章中進行了敘述 簡而言之,bucket_type如下所示:

  struct bucket_type_impl
  {
    spinlock<unsigned char> lock;  // = 2 if you need to reload the bucket list
    atomic<unsigned> count; // count is used items in there
    std::vector<item_type, item_type_allocator> items;
    bucket_type_impl() : count(0), items(0) {  }
    ...

然而線程消毒者聲稱在bucket_type的構造和它的第一次使用之間存在競爭,特別是當加載計數原子時。 事實證明,如果你通過它的構造函數初始化std :: atomic <>, 那么初始化不是原子的 ,因此內存位置不是原子釋放的,因此對其他線程不可見,這是違反直覺的,因為它是一個原子,並且大多數原子操作默認為memory_order_seq_cst。 因此,您必須在構造之后顯式執行發布存儲,以使用其他線程可見的值初始化原子。

是否有一些非常迫切的原因,為什么std :: atomic與值消耗構造函數不會使用發布語義初始化自己? 如果沒有,我認為這是一個庫缺陷。

編輯: Jonathan的答案對於歷史來說更好,但是ecatmur的回答鏈接到Alastair關於此事的缺陷報告,以及如何通過簡單地添加注釋來表示構造提供其他線程的可見性。 因此,我將獎勵ecatmur。 感謝所有回復的人,我認為要求一個額外的構造函數的方式很明顯,它至少會在文檔中脫穎而出,值得使用構造函數。

編輯2:我最終將此作為C ++語言中的缺陷與委員會一起提出,並且並發部分主持人Hans Boehm認為這不是問題,原因如下:

  1. 2014年沒有現有的C ++編譯器將消費視為與獲取不同。 正如您將永遠不會,在現實世界的代碼中,將原子傳遞給另一個線程而不經過一些釋放/獲取,原子的初始化將使用原子對所有線程可見。 我覺得這很好,直到編譯器趕上來,在此之前,Thread Sanitiser會對此發出警告。

  2. 如果你像我一樣做了不匹配的消費 - 獲取 - 釋放(我正在使用一個release-inside-lock / consume-outside-lock原子來推測性地避免發布 - 獲取自旋鎖,這是不必要的)那么你就是一個大的足夠的男孩知道你必須在施工后手動儲存釋放原子。 這可能是一個公平的觀點。

這是因為轉換構造函數是constexprconstexpr函數不能具有原子語義等副作用。

DR846中 ,Alastair Meredith寫道:

我不確定是否使用constexpr關鍵字(它限制了構造函數的形式)暗示了初始化,但即使是這種情況,我認為值得明確拼寫,因為推斷會過於微妙。案件。

該缺陷的解決方案(勞倫斯·克勞爾)是用構造函數記錄的:

[ 注意:構造不是原子的。 - 尾注 ]

然后將該注釋擴展為當前的措辭,給出了memory_order_relaxed可能的內存競爭(通過memory_order_relaxed原子地址的memory_order_relaxed操作)的示例

轉換構造函數需要constexpr原因(主要)是允許靜態初始化。 DR768中我們看到:

進一步的討論:為什么ctor被標記為“constexpr”? Lawrence [Crowl]說這允許對象靜態初始化,這很重要,否則初始化會出現競爭條件。

因此:使構造函數constexpr消除靜態生命周期對象上的競爭條件,代價是動態生命周期對象中的競爭只發生在相當人為的情況下,因為競爭發生在動態生命周期原子對象的內存位置必須以不會導致原子對象的也同步到該線程的方式傳遞給另一個線程。

這是一個有意的設計選擇(在關於它的標准警告中甚至還有一個注釋),我認為它是為了與C兼容而完成的。

C ++ 11原子的設計使它們也可以被WG14用於C,使用非成員函數,例如atomic_load ,類型如atomic_int而不是C ++的成員函數 - 只有std::atomic<int> 在原始設計中, atomic_int類型沒有特殊屬性,只有atomic_load()和其他函數才能實現原子性。 在該模型中, atomic_init不是原子操作,它只是初始化POD。 只有后續的atomic_store(&i, 1)調用atomic_store(&i, 1)原子的。

最終,WG14決定做不同的事情,加上_Atomic符這使得atomic_int類型具有魔法屬性。 我不確定這是否意味着C atomics的初始化可能是原子的(因為它,C11中的atomic_init和C ++ 11被記錄為非原子的),所以C ++ 11規則可能是不必要的。 我懷疑人們會認為有很好的性能原因可以保持初始化非原子性,正如上面的interjay的評論所說,你需要向另一個線程發送一些通知,告知構造了obejct並准備好從中讀取,以便通知可以介紹必要的圍欄。 std::atomic初始化做一次,然后第二次說構造對象可能是浪費。

我會說,這是因為構造永遠不是一個線程通信操作:當你構造一個對象時,你用以明智的值填充以前未初始化的內存。 除非構造線程明確地傳達,否則另一個線程無法判斷該操作是否已完成。 如果你無論如何都在進行建築競賽,你會立即出現未定義的行為。

由於創建線程必須在允許另一個線程使用它之前顯式地發布其構造值的成功,因此在同步構造函數中沒有任何意義。

暫無
暫無

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

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