該標準定義了幾個“發生在之前”的關系,這些關系將舊的“之前排序”擴展到多個執行緒:
[intro.races]11 評估 A只發生在評估 B之前,如果
(11.1) — A 在 B 之前排序,或
(11.2) — A 與 B 同步,或
(11.3) — A 發生在 X 之前,X 發生在 B 之前。[注10:在沒有消費操作的情況下,發生在之前和簡單地發生在關系相同之前。— 尾注]
12 評估 A強烈發生在評估 D之前,如果,
(12.1) — A 在 D 之前被排序,或
(12.2) — A 與 D 同步,并且 A 和 D 都是順序一致的原子操作 ([atomics.order]),或
(12.3) — 有評估 B 和 C,例如A 在 B 之前被排序,B 只是在 C 之前發生,并且 C 在 D 之前被排序,或者
(12.4)——有一個評估 B,使得 A 在 B 之前強烈發生,B 在 D 之前強烈發生。[注 11:非正式地,如果 A 在 B 之前強烈發生,那么在所有背景關系中 A 似乎都在 B 之前被評估。在排除消費操作之前強烈發生。— 尾注]
(粗體我的)
兩者之間的區別似乎非常微妙。對于匹配對或釋放-獲取操作(除非兩者都是 seq-cst),“強烈發生在之前”從來都不是真的,但它仍然以某種方式尊重釋放-獲取同步,因為在發布之前排序的操作“強烈發生在”操作之前匹配獲取后排序。
為什么這種差異很重要?
“強發生在之前”是在 C 20 中引入的,在 C 20 之前,“簡單發生在之前”曾經被稱為“強發生在之前”。為什么要介紹?
[atomics.order]/4 說所有 seq-cst 操作的總順序與“強發生在之前”一致。
這是否意味著它與“之前發生過”不一致?如果是,為什么不呢?
我忽略了簡單的“發生在之前”,因為它與“之前發生過”的區別僅在于其處理memory_order_consume,暫時不鼓勵使用它,因為顯然大多數(所有?)主要編譯器將其視為memory_order_acquire.
我已經看過這個 Q&A,但它沒有解釋為什么“強烈發生在之前”存在,也沒有完全說明它的含義(它只是說明它不尊重發布 - 獲取同步,這不是完全如此)。
找到了引入“之前發生過”的提議。
我不完全理解它,但它解釋了以下內容:
- “強烈發生在之前”是“簡單發生在之前”的弱化版本。
- 只有當 seq-cst 與 aqc-rel 在同一變數上混合時才能觀察到差異(我認為,這意味著當獲取負載從 seq-cst 存盤讀取值時,或者當 seq-cst 負載從一個發布商店)。但我仍不清楚將兩者混合的確切效果。
uj5u.com熱心網友回復:
這是我目前的理解,可能不完整或不正確。驗證將不勝感激。
C 20 重命名strongly happens before為simply happens before,并為 引入了一個新的、更寬松的定義strongly happens before,從而減少了排序。
Simply happens before用于推斷代碼中是否存在資料競爭。(實際上,那將是簡單的“之前發生過”,但是在沒有消耗操作的情況下,兩者是等效的,標準不鼓勵使用消耗操作,因為大多數(所有?)主要編譯器將它們視為獲取操作。)
較弱strongly happens before的用于推理 seq-cst 操作的全域順序。
此更改在提案P0668R5:修訂 C 記憶體模型中引入,該提案基于Lahav 等人的論文Repairing Sequential Consistency in C/C 11(我沒有完全閱讀)。
該提案解釋了進行更改的原因。長話短說,大多數編譯器在 Power 和 ARM 架構上實作原子的方式在極少數情況下被證明是不一致的,并且修復編譯器會產生性能成本,因此他們修復了標準。
如果您將 seq-cst 操作與對同一原子變數的獲取-釋放操作混合使用(即,如果獲取操作從 seq-cst 存盤中讀取值,或者 seq-cst 操作從發布中讀取值),該更改只會影響您店鋪)。
如果您不以這種方式混合操作,那么您不會受到影響(即可以將simply happens before和strongly happens before視為等效)。
變化的要點是,一個 seq-cst 操作和相應的獲取/釋放操作之間的同步不再影響這個特定的 seq-cst 操作在全域 seq-cst 順序中的位置,但同步本身仍然存在。
這使得此類 seq-cst 操作的 seq-cst 順序非常沒有實際意義,見下文。
該提案提供了以下示例,我將嘗試解釋我對它的理解:
atomic_int x = 0, y = 0;
int a = 0, b = 0, c = 0;
// Thread 1
x.store(1, seq_cst);
y.store(1, release);
// Thread 2
b = y.fetch_add(1, seq_cst); // b = 1 (the value of y before increment)
c = y.load(relaxed); // c = 3
// Thread 3
y.store(3, seq_cst);
a = x.load(seq_cst); // a = 0
注釋指出了此代碼可以執行的一種方式,該標準過去曾禁止(在此更改之前),但實際上可能會在受影響的架構上發生。
執行程序如下:
.-- T3 y.store(3, seq_cst); --. (2)
| | | strongly
| | sequenced before | happens
| V | before
| T3 a = x.load(seq_cst); // a = 0 --. <-' (3)
| : coherence-
| : ordered
| : before
| T1 x.store(1, seq_cst); <-' --. --. (4)
| | |st |
| | sequenced before |h |
| V |b |
| . T1 y.store(1, release); <-' |
| | : | strongly
| | : synchronizes with | happens
| | V | before
| > T2 b = y.fetch_add(1, seq_cst); // b = 1 --. | (1)
| | |st |
| | sequenced before |h |
| V |b |
'-> T2 c = y.load(relaxed); // c = 3 <-' <-'
在哪里:
右側括號中的數字顯示全域 seq-cst 順序。
左側的箭頭顯示值如何在某些加載和存盤之間傳播。
中間的箭頭顯示:
- 'Sequenced before',舊的單執行緒評估順序。
- 'Synchronizes with',釋放-獲取同步(seq-cst 加載計為獲取操作,seq-cst 存盤計為釋放操作)。
這兩者一起構成“之前發生過”。
右側的箭頭基于中間的箭頭,它們顯示:
新重新定義的“強烈發生在之前”關系。
'Coherence-ordered before',本提案中引入的新關系,僅用于定義全域 seq-cst 順序,并且顯然不強加同步(與釋放-獲取操作不同)。
似乎它包括除了影響全域 seq-cst 順序的“簡單發生之前”之外的所有內容。在這種情況下,如果加載沒有看到存盤寫入的值,則加載在存盤之前進行,這只是常識。
全域 seq-cst 順序與兩者一致。
請注意,在這張圖片上,之前沒有發生任何強烈的事情b = y.fetch_add(1, seq_cst);,因此在全域 seq-cst 順序中沒有任何東西必須在它之前,因此可以將其向上移動到 seq-cst 順序的開頭,即最終發生的是這種情況,即使它讀取稍后(按此順序)操作產生的值。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/403034.html
標籤:
上一篇:存盤執行緒的函式輸入
