這篇文章主要講解了“C++的memory order怎么理解”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“C++的memory order怎么理解”吧!
Key Points:
- 要從"防止編譯器重排"與"防止CPU亂序"兩個角度去理解 memory_order
- release semantics 一言以蔽之,一般表示 "最后 Store"
- acquire semantics
一言以蔽之,一般表示 "最先 Load"
- 注意 x86_64 CPU 一般^1都滿足 strong memory model 特性:CPU 四種可能發生的亂序中,只允許StoreLoad亂序(或稱為StoreLoad重排)
1)與 atomic 連用
memory_order_relaxed:atomic variable 的 relaxed 操作和普通變量普通操作有何區別?
- 普通變量的SL操作并不保證原子性,而 atomic 變量的所有操作都是保證原子性的。(TODO: 甚至不會被中斷打斷?)
- x86_64 實測:atomic 變量結合任意 memory_order 都能有效防止編譯器重排。
memory_order_consume:所有后續 data-dependent 的 S/L 操作禁止被 re-order 到本 L 前面。和 release-S 連用。
memory_order_acquire:所有后續 S/L 操作禁止被 re-order 到本 L 前面。和 release-S 連用。
- acquire 操作強調本 L 一定是"最先 Load"。
x86_64 實測:
- a.load(acquire/relaxed) 產生的機器碼相同。猜測是由于 x86_64 的 strong-memory-model 性質,禁止
L-S 和
L-L 重排,其任何 Load 都具有 acquire 語義。
- UB 操作:a.store(acquire),則機器碼會在 S 后面添加一個 mfence。此時實際產生的語義為:所有后續 S/L 操作禁止被 re-order 到本 S 前面。猜測是由于 x86_64 CPU 允許 S-L re-order,那么要保證此 S 后面的 S/L 不被重排到本 S 前,那么只能加入 fence 指令。
- UB 操作:a.load(release) 產生的機器碼等同于 a.load(acquire)。(注意并不符合類似 S-release 的語義 "所有前面 S/L 操作禁止被 re-order 到本 L 后面,如果要符合的話必須在本 L 前面加 mfence)"
- UB 操作:a.load() 接所有 memory_order 都等效于 a.load(acquire),即編譯產生同樣的機器碼。
memory_order_release:所有前面 S/L 操作禁止被 re-order 到本 S 后面。和 acquire/consume-L 連用。
- release 操作強調本 S 操作一定是"最后 Store"。
x86_64 實測:
- a.store(release/relaxed) 產生的機器碼相同。猜測是由于 x86_64 的 strong-memory-model 性質,禁止 S-
S 和 L-
S 重排,其任何 Store 都具有 release 語義。
- UB 操作:a.store(acquire/consume/acq_rel/seq_cst) 產生的機器碼相同,都是在本 S 后面加入一個
mfence
指令。猜測是由于 x86_64 中,只需要在 S 后面加一個 mfence,該 S 就能同時滿足"本 S 前所有的 S/L 禁止被重排到本 S 之后(即 release 語義)"與"本 S 后所有的 S/L 禁止被重排到本 S 之前(類似 L-acquire 語義)"的兩個條件
memory_order_acq_rel:適用于RMW操作。
- 1)本線程所有 S/L 操作(無論前后)禁止被 re-order 到本 S 前或后。
- 為什的只提到S?(TODO: 這個RMW操作一定是保證原子性的嗎?)
- 其實是只需要關心 S 即可。因為RMW操作的特性類似一個 LS 操作,這里 S 已經作為了同步點,本線程 L 前的 S/L 即使重排也只能重排在本 L 后且本 S 前,即使發生這種重排也是沒有關系的。而本 S 后所有的 S/L 都不可被重排到本 S 前,這樣就間接保證了不可被重排到本 L 前,從而也保證了 acquire 語義。
- 2)執行了相應 release 操作的其他線程,其 release 前的所有 S 一定發生在本 S 前。
- 這句話其實暗含了其他線程造成的該原子變量本身的 S 一定是發生在本 L 前的。因為如果其他線程對該原子變量的改動還沒有被本線程觀察到的話,可以認為其他線程并沒有寫它,也就不存在同步問題。既然觀察到了變化,那么這個變化伴隨的更改順序關系才需要被討論。
- 由于 S 比 L 慢,因此這個語義只要求其他線程被 release 的 S 一定是發生在本 S 之前就可以,沒必要嚴格到必須發生在本 L 之前,那樣效率會變低。不過這個要求已經比單純的 acquire 嚴格了,即要求其他線程被 release 的 S 可以發生在本 L 后但必須發生在本 S 前,而單純的 acquire 只暗含本 L 之后,就能看到相關線程所有被 release 的 S 了
- 術語情景:x=1; y.store(1, release); 此時稱為 x=1 和 y=1 這些 S 最后執行了 release 操作。或者說 x=1 和 y=1 都是被該線程 release 的 S 操作,且同步點是 Y。
memory_order_seq_cst:
- 對于 RMW 執行 acq_rel 語義,并且對所有線程執行了 seq_cst 操作的 S,都存在一個 TSO
- TSO:Total Single Order,所有線程觀察到的 S 順序都是一樣的?
- seq_cst 與 load 操作連用時,與所有 memory_order 都等效。即編譯產生同樣的機器碼。
- seq_cst 與 store 操作連用時,與 acquire/consume/acq_rel 等效。
感謝各位的閱讀,以上就是“C++的memory order怎么理解”的內容了,經過本文的學習后,相信大家對C++的memory order怎么理解這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!