?
本文檔使用 php中文網(wǎng)手冊 發(fā)布
在頭文件<stdatomic.h>中定義 | ||
---|---|---|
enum memory_order {memory_order_relaxed,memory_order_consume,memory_order_acquire,memory_order_release,memory_order_acq_rel,memory_order_seq_cst}; | (自C11以來) |
memory_order
指定如何在原子操作周圍定期進行非原子內(nèi)存訪問。在多核系統(tǒng)上不存在任何約束時,當(dāng)多個線程同時讀取和寫入多個變量時,一個線程可以按照與另一個線程寫入它們的順序不同的順序觀察值的變化。事實上,在多個讀者線程中,更改的順序甚至可能不同。由于內(nèi)存模型允許的編譯器轉(zhuǎn)換,即使在單處理器系統(tǒng)上也會出現(xiàn)類似的效果。
語言和庫中的所有原子操作的默認(rèn)行為提供了順序一致的排序(參見下面的討論)。該默認(rèn)值可能會損害性能,但可以給庫的原子操作提供額外的memory_order
參數(shù),以指定編譯器和處理器必須為該操作強制執(zhí)行的確切約束,而不僅限于原子性。
| 在頭文件<stdatomic.h> |中定義
|:----|
| Value | Explanation |
| memory_order_relaxed | 輕松的操作:對其他讀取或?qū)懭霙]有同步或排序約束,只保證此操作的原子性(請參閱下面的輕松排序)。
| memory_order_consume | 使用此內(nèi)存順序的加載操作會在受影響的內(nèi)存位置執(zhí)行消耗操作:根據(jù)當(dāng)前加載的值,當(dāng)前線程中的讀取或?qū)懭氩僮髟诖思虞d之前可以重新排序。在釋放相同原子變量的其他線程中寫入數(shù)據(jù)相關(guān)變量在當(dāng)前線程中可見。在大多數(shù)平臺上,這僅影響編譯器優(yōu)化(請參閱下面的Release-Consume命令)|
| memory_order_acquire | 使用此內(nèi)存順序的加載操作對受影響的內(nèi)存位置執(zhí)行獲取操作:在此加載之前,當(dāng)前線程中的讀取或?qū)懭氩僮鞑荒苤匦屡判?。在?dāng)前線程中可以看到釋放相同原子變量的其他線程中的所有寫入操作(請參閱下面的Release-Acquire命令)|
| memory_order_release | 與此存儲器順序的存儲操作執(zhí)行釋放操作:沒有讀取或在當(dāng)前線程可以在此存儲之后被重新排序?qū)懭?。在?dāng)前線程所有寫是在獲得相同的原子變量其他線程可見(見發(fā)布 - 采集以下的訂購)和寫攜帶的依賴到原子變量中消耗相同的原子其他線程變得可見(見釋放,消費訂購如下)。|
| memory_order_acq_rel | 使用該存儲器命令的讀取 - 修改 - 寫入操作既是獲取操作又是釋放操作。在當(dāng)前線程中沒有內(nèi)存讀取或?qū)懭肟梢栽谠摯鎯χ盎蛑笾匦屡判颉T谄渌€程中釋放相同原子變量的所有寫操作在修改之前都是可見的,并且修改在獲取相同原子變量的其他線程中可見。|
| memory_order_seq_cst | 此存儲器訂單的任何操作都是采集操作和釋放操作,并且存在單個總訂單,其中所有線程都以相同的順序觀察所有修改(請參見下面的按順序一致的排序)。
標(biāo)記的原子操作memory_order_relaxed
不是同步操作; 它們不會在并發(fā)內(nèi)存訪問中強加一個順序。他們只保證原子性和修改順序的一致性。
例如,對于x
和y
最初為零,
// Thread 1:
r1 =
atomic_load_explicit(y, memory_order_relaxed);
// A
atomic_store_explicit(x, r1, memory_order_relaxed);
// B
// Thread 2:
r2 =
atomic_load_explicit(x, memory_order_relaxed);
// C
atomic_store_explicit(y, 42, memory_order_relaxed);
// D
.
被允許產(chǎn)生r1 == r2 == 42
,因為盡管A被測序-之前線程1中B和C 之前測序線程2內(nèi)d,沒有什么阻止d從在y的修改次序出現(xiàn)A之前和B從在修改次序出現(xiàn)在C之前的x。線程1中的負載A可以看到D對y的副作用,而線程2中的負載C可以看到B對x的副作用。
松散內(nèi)存排序的典型用法是遞增計數(shù)器,如引用計數(shù)器,因為這只需要原子性,但不需要排序或同步(注意遞減shared_ptr計數(shù)器需要與析構(gòu)函數(shù)進行獲取釋放同步)。
如果線程A中的原子存儲被標(biāo)記memory_order_release
并且來自同一變量的線程B中的原子加載被標(biāo)記memory_order_consume
,則所有存儲器在從原子存儲之前寫入(非原子和放寬原子)線程A 在線程B中的那些操作內(nèi)成為可見的副作用,負載操作攜帶依賴性,即一旦原子加載完成,線程B中的那些使用從加載獲得的值的運算符和函數(shù)被保證為看看寫到內(nèi)存的線程是什么。
同步僅在釋放和使用相同原子變量的線程之間建立。其他線程可以看到不同的存儲器訪問順序,而不是任何一個或兩個同步線程。
在DEC Alpha以外的所有主流CPU上,依賴性排序是自動的,不會為此同步模式發(fā)出額外的CPU指令,只會影響某些編譯器優(yōu)化(例如,禁止編譯器對涉及依賴項的對象執(zhí)行推測性加載鏈)。
這種排序的典型用例涉及讀取訪問很少寫入的并發(fā)數(shù)據(jù)結(jié)構(gòu)(路由表,配置,安全策略,防火墻規(guī)則等)以及使用指針中介發(fā)布的發(fā)布者訂閱者情況,也就是說,當(dāng)生產(chǎn)者發(fā)布指針時消費者可以訪問這些信息:不需要將生產(chǎn)者寫入內(nèi)存的所有內(nèi)容都寫入消費者可以看到的內(nèi)存中(這可能是對弱排序架構(gòu)的昂貴操作)。這種情況的一個例子是rcu_dereference。
請注意,目前(2015年2月)沒有已知的生產(chǎn)編譯器跟蹤依賴鏈:消耗操作被解除以獲取操作。
如果一些原子被存儲釋放并且其他幾個線程對該原子執(zhí)行讀 - 修改 - 寫操作,則形成“release sequence”:執(zhí)行讀取 - 修改的所有線程 - 寫入相同的原子與第一線程同步并且即使他們沒有memory_order_release
語義,也是如此。這使單個生產(chǎn)者 - 多個消費者情況成為可能,而不會在各個消費者線程之間施加不必要的同步。
如果線程A中的原子存儲被標(biāo)記memory_order_release
并且來自同一變量的線程B中的原子加載被標(biāo)記memory_order_acquire
,則所有存儲從線程A的角度在原子存儲之前寫入(非原子和放寬原子)在線程B中變成可見的副作用,也就是說,一旦完成了原子加載,線程B就會保證看到線程A寫入內(nèi)存的所有內(nèi)容。
同步僅在釋放和獲取相同原子變量的線程之間建立。其他線程可以看到不同的存儲器訪問順序,而不是任何一個或兩個同步線程。
在高度有序的系統(tǒng)(x86,SPARC TSO,IBM大型機)上,大多數(shù)操作的發(fā)布采集排序是自動的。對于此同步模式,不會發(fā)出額外的CPU指令,只會影響某些編譯器優(yōu)化(例如,禁止編譯器將原子存儲釋放移出原子存儲釋放或在原子載入獲取之前執(zhí)行非原子加載)。在弱有序的系統(tǒng)(ARM,Itanium,PowerPC)上,必須使用特殊的CPU負載或內(nèi)存防護指令。
相互排斥鎖(如互斥鎖或原子螺旋鎖)是發(fā)布 - 獲取同步的一個示例:當(dāng)鎖由線程A釋放并由線程B獲取時,上下文中關(guān)鍵部分(釋放之前)發(fā)生的所有事件線程A必須對正在執(zhí)行相同關(guān)鍵部分的線程B(獲取之后)可見。
原子操作memory_order_seq_cst
不僅以與釋放/獲取順序相同的方式標(biāo)記內(nèi)存(發(fā)生的所有事情 -在一個線程中的存儲變成執(zhí)行加載的線程中的可見副作用之前),而且還建立了所有的修改順序原子操作如此標(biāo)記。
從形式上看,
memory_order_seq_cst
從原子變量M加載的每個操作B都遵循以下之一:
最后一個操作A的結(jié)果是修改后的M,它出現(xiàn)在單個總訂單中的B之前
或者,如果有這樣一個A,那么B可以觀察到M上的一些修改的結(jié)果,memory_order_seq_cst
而不是在 A 之前發(fā)生并且不發(fā)生
或者,如果沒有這樣的A,則B可以觀察到M的一些不相關(guān)的修改的結(jié)果 memory_order_seq_cst
如果在 B 之前有一個排序的memory_order_seq_cst
atomic_thread_fence
操作X ,則B會觀察以下之一:
memory_order_seq_cst
在單個總訂單中出現(xiàn)在X之前的M 的最后修改
M的修改順序中稍后出現(xiàn)的一些與M無關(guān)的修改
對于M上的一對稱為A和B的原子操作,其中A寫入并且B讀取M的值,如果有兩個memory_order_seq_cst
atomic_thread_fence
s X和Y,并且如果A被排序 - 在 X 之前,Y被排序 - 在 B 之前,并且X出現(xiàn)在單一全部訂單中Y之前,則B觀察到:
A的效果
M在M的修改順序中出現(xiàn)在A后面的一些不相關(guān)的修改
對于稱為A和B的M的一對原子修飾,B在M的修飾順序為A之后發(fā)生。
有一個memory_order_seq_cst
atomic_thread_fence
X使得A被排序 - 在 X和X出現(xiàn)在單個全部順序中的B之前
或者,有一個memory_order_seq_cst
atomic_thread_fence
Y使得Y 排序 - 在 B和A之前出現(xiàn)在單個總排序中的Y 之前
或者,存在memory_order_seq_cst
atomic_thread_fence
S和X,使得A被排序 - 在 X 之前,Y 在 B 之前被排序,并且X在單個總排序中出現(xiàn)在Y之前。
請注意,這意味著:
1)一旦沒有標(biāo)記的原子操作memory_order_seq_cst
進入圖片,就會失去順序一致性
2)順序一致的柵欄只為柵欄本身建立總排序,而不是針對一般情況下的原子操作(順序 - 之前不是跨線程關(guān)系,不像以前發(fā)生的那樣)
對于多個生產(chǎn)者 - 多個消費者情況,序列排序可能是必要的,其中所有消費者都必須遵守以相同順序出現(xiàn)的所有生產(chǎn)者的行為。
全部順序排序需要在所有多核系統(tǒng)上提供完整的內(nèi)存圍欄CPU指令。這可能會成為性能瓶頸,因為它會迫使受影響的內(nèi)存訪問傳播到每個內(nèi)核。
在一個執(zhí)行線程中,通過易變的左值訪問(讀寫)不能重新排序超過由相同線程中的序列點分隔的可觀察副作用(包括其他易失性訪問),但不保證此順序被觀察到由另一個線程,因為易失性訪問不建立線程間同步。
此外,易失性訪問不是原子性的(并發(fā)讀寫是數(shù)據(jù)競爭),也不會對內(nèi)存進行排序(非易失性內(nèi)存訪問可以在易失性訪問周圍自由重新排序)。
一個值得注意的例外是Visual Studio,其中默認(rèn)設(shè)置下,每個易失性寫入都具有釋放語義,每個易失性讀取都獲取語義(MSDN,因此揮發(fā)性可用于線程間同步,標(biāo)準(zhǔn)volatile
語義不適用于多線程編程,盡管它們足以用于與signal
應(yīng)用于sig_atomic_t
變量時在同一線程中運行的處理程序進行通信。
C11標(biāo)準(zhǔn)(ISO/IEC 9899:2011):
7.17.1/4 memory_order(p: 273)
7.17.3訂單和一致性(p: 275-277)
| 用于記憶順序的C ++文檔|