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