C ++ 11 記憶體模型
如果至少一個操作是修改(也稱為儲存操作 ),則嘗試訪問相同儲存器位置的不同執行緒參與資料競爭。這些資料爭用導致未定義的行為。為了避免它們,需要防止這些執行緒同時執行這種衝突操作。 ** ** **
同步原語(互斥,臨界區等)可以保護這種訪問。C++ 11 中引入的記憶體模型定義了兩種新的可移植方式,用於在多執行緒環境中同步對記憶體的訪問:原子操作和圍欄。
原子操作
現在可以通過使用原子載入和原子儲存操作來讀取和寫入給定的記憶體位置。為方便起見,這些包含在 std::atomic<t>
模板類中。此類包含 t
型別的值,但這次載入和儲存到物件是原子的。
該模板不適用於所有型別。哪些型別可用是特定於實現的,但這通常包括大多數(或所有)可用的整數型別以及指標型別。所以 std::atomic<unsigned>
和 std::atomic<std::vector<foo> *>
應該是可用的,而 std::atomic<std::pair<bool,char>>
很可能不會。
原子操作具有以下屬性:
- 所有原子操作都可以從多個執行緒同時執行,而不會導致未定義的行為。
- 一個原子負荷會看到其任一原子物件用構造的初始值,或經由一些寫入它的價值原子商店操作。
- ** 對同一原子物件的原子儲存在所有執行緒中的排序相同。如果某個執行緒已經看到某個原子儲存操作的值,則後續的原子載入操作將看到相同的值或後續原子儲存操作所儲存的值。
- 原子讀 - 修改 - 寫操作允許原子載入和原子儲存發生,而不需要其他原子儲存。例如,可以從多個執行緒原子地遞增計數器,並且不管執行緒之間的爭用如何都不會丟失增量。
- 原子操作接收可選的
std::memory_order
引數,該引數定義操作對其他儲存器位置的附加屬性。
std::memory_order | 含義 |
---|---|
std::memory_order_relaxed |
沒有其他限制 |
std::memory_order_release →std::memory_order_acquire |
如果 load-acquire 看到 store-release 儲存的值,那麼儲存在 store-release 發生之前排序,然後在負載獲取後的負載**排序之前 ** |
std::memory_order_consume |
像 memory_order_acquire 但僅適用於依賴負載 |
std::memory_order_acq_rel |
結合了 load-acquire 和 store-release |
std::memory_order_seq_cst |
順序一致性 |
這些記憶體順序標記允許三種不同的記憶體排序規則: 順序一致性,放鬆和釋放 - 獲取與其兄弟版本消耗。
順序一致性
如果沒有為原子操作指定記憶體順序,則順序預設為順序一致性。也可以通過使用 std::memory_order_seq_cst
標記操作來明確選擇此模式。
使用此順序,沒有記憶體操作可以跨越原子操作。在原子操作發生之前,所有記憶體操作都按順序執行,原子操作發生在所有在其後排序的記憶體操作之前。這種模式可能是最容易推理的模式,但它也會導致對效能的最大懲罰。它還會阻止所有可能嘗試重新排序原子操作之外的操作的編譯器優化。
輕鬆的訂購
與順序一致性相反的是寬鬆的記憶體排序。它是使用 std::memory_order_relaxed
標籤選擇的。輕鬆的原子操作不會對其他記憶體操作施加任何限制。剩下的唯一影響是操作本身仍然是原子的。
釋出 - 獲取訂購
一個原子商店操作可標記為 std::memory_order_release
和原子負荷操作可以 std::memory_order_acquire
被標記。第一個操作稱為 *(原子)儲存釋放,*而第二個操作稱為 (原子)load-acquire 。
當 load-acquire 看到由 store-release 寫入的值時,會發生以下情況:所有在 store-release 之前排序的儲存操作變得可見( 在之前發生 )在 load-acquire 之後排序的載入操作。
原子讀 - 修改 - 寫操作也可以接收累積標籤 std::memory_order_acq_rel
。這使得原子負載的操作的一部分的原子負載獲取而原子商店部分成為原子商店釋放。
在原子儲存釋放操作之後,不允許編譯器移動儲存操作。在原子載入 - 獲取 (或載入 - 消耗 ) 之前,也不允許移動載入操作。
另請注意,沒有原子載入釋放或原子儲存獲取。嘗試建立此類操作使他們放鬆操作。
釋出 - 消費訂購
這種組合類似於 release-acquire ,但這次原子載荷用 std::memory_order_consume
標記併成為 (原子)負載消耗操作。此模式是相同的釋放 -獲得唯一的區別是,之後測序負載操作中的負載消耗只有這些取決於由裝入的值負載消耗是有序的。
柵欄
Fences 還允許線上程之間排序記憶體操作。圍欄可以是釋放圍欄或獲取圍欄。
如果釋放圍欄在獲取圍柵之前發生,則在釋放圍欄之前排序的儲存對於在獲取圍欄之後排序的載荷可見。為了保證釋放圍欄在獲取圍欄之前發生,可以使用其他同步原語,包括放鬆原子操作。