這是從C++11中引深而來的座韵,在C++11標(biāo)準(zhǔn)庫中提供了atomic的原子操作煌张。而其中函數(shù)參數(shù)中有一項(xiàng)是用于指定內(nèi)存順序的。
什么是內(nèi)存順序副签?內(nèi)存順序描述了計(jì)算機(jī)CPU指令訪問內(nèi)存的順序山析。這個(gè)順序和我們通常的代碼順序存在一定的差異堰燎,從而導(dǎo)致在使用多核進(jìn)行多線程編程的情況下可能會(huì)引發(fā)問題。編譯器優(yōu)化和CPU指令都影響這該順序笋轨。比如操作A秆剪,B,C爵政,D四個(gè)CPU操作仅讽。CPU的執(zhí)行順序可能是A->B->C->D或者B->A->C->D等四個(gè)操作的排列組合。同時(shí)不同的CPU間的順序也可能是不一樣的钾挟。
從而在內(nèi)核中引入了barriers(柵欄洁灵,屏障?)掺出,whatever徽千,其本質(zhì)就是一道如同籬笆的隔離機(jī)制,將原本random的內(nèi)存訪問順序變得有組織起來汤锨。這樣做的同時(shí)當(dāng)然是降低了一定性能双抽,畢竟要多執(zhí)行一些操作,但是在并發(fā)編程的情況下闲礼,這是保證數(shù)據(jù)一致性所必須的牍汹。引入barriers之后的操作將變成:memory
barriers->A->B->C->D->memory barriers。使得結(jié)果在多線程并發(fā)的情況下符合預(yù)期柬泽。
C++11標(biāo)準(zhǔn)庫中提供了六種不同的memory order柑贞。即memory_order_relaxed(松散順序)、memory_order_seq_cst(順序一致)聂抢、memory_order_consume、memory_order_acquire棠众、memory_order_release琳疏、memory_order_acq_rel(獲得-釋放順序)。
順序一致:意味著和程序的行為和簡單的順序世界觀是一致的闸拿。舉個(gè)不太恰當(dāng)?shù)睦泳褪窍扔心銧敔斂张危儆心惆郑缓笥心阈禄纭7催^來這個(gè)順序不成立揽趾。順序一致是默認(rèn)的采用的內(nèi)存訪問順序。這也是最嚴(yán)格的一種內(nèi)存順序苛骨。其上的所有多線程并發(fā)看起來就像是一個(gè)線程在執(zhí)行篱瞎,因而其效率損耗也是最大的苟呐。
舉個(gè)例子:
如上面的這段代碼,能夠確保assert永遠(yuǎn)不會(huì)發(fā)生俐筋,但是z的值有可能是1也有可能是2牵素。
松散順序:只保證當(dāng)前操作的原子性,不考慮線程間的同步澄者,其他線程可能讀到新值也可能讀到舊值笆呆。比如share_ptr中的引用計(jì)數(shù),只關(guān)心當(dāng)前的應(yīng)用數(shù)量粱挡,而不關(guān)心誰在引用誰在解引用赠幕。
舉個(gè)例子:
上例中assert是有可能觸發(fā)的。因?yàn)閤.load可能讀到false询筏,即使在y已經(jīng)存儲(chǔ)了true的情況下榕堰。a和b是不同的線程,它們之間的內(nèi)存順序可能是不一致的屈留,因此即使b已經(jīng)讀到了y的值為true局冰,它也不一定能夠讀到x的值為true,即使a已經(jīng)將x的值存儲(chǔ)為true了灌危。這個(gè)在x86下比較難調(diào)試出來康二,我實(shí)了很多遍都沒調(diào)試出來。據(jù)說android下可以很容易觸發(fā)勇蝙。
獲取-釋放順序:
獲取-釋放順序是松散順序的進(jìn)步油额,操作在多線程間仍然沒有總的順序,但是引入了一些同步機(jī)制胖烛。
原子載入(load)是獲取操作(memory_order_acquire)
原子存儲(chǔ)(store)是釋放操作(memory_order_release)
原子的讀-修改-寫操作(fetch_add/exchange)是獲取谬莹,釋放或兩者兼?zhèn)?memory_order_acq_rel)
釋放操作與讀取寫入值的獲取操作同步。這意味著翁锡,不同的線程仍然可以看到不同的排序蔓挖,但是這些順序是受到限制的。
傳遞性:如果線程A中的操作發(fā)生于線程B之前馆衔,并且B中的操作發(fā)生于C之前瘟判,則A線程發(fā)生于C之前。傳遞關(guān)系的前提條件是A,B間角溃,B,C間存在同步關(guān)系拷获。
Memory_order_release:
對寫入施加release語義(store),在代碼中這條語句前面的所有讀寫操作都無法被重排(reorder)到這個(gè)操作之后减细。
當(dāng)前線程內(nèi)的所有寫操作匆瓜,對于其它對這個(gè)原子變量進(jìn)行acquire的線程可見
當(dāng)前線程內(nèi)的所有寫操作,對于其它對這個(gè)原子變量進(jìn)行consume的線程可見
Memory_order_acquire:
對于施加acquire(load),在代碼中這條語句后面所有讀寫操作都無法重排到這個(gè)操作之前驮吱。
在這個(gè)原子變量上施加release語義的操作發(fā)生之后茧妒,acquire可以保證讀到所有在release前發(fā)生的寫入。
內(nèi)核中提供了四種內(nèi)存屏障:
Write(或store) memory barriers:
用于保障所有在內(nèi)存屏障之前的STORE操作將比所有屏障后的STORE操作先發(fā)生糠馆。
1.1 write
barriers通常只對stores操作的順序有影響嘶伟,而對loads的操作沒有影響
1.2 通常需要配合read barriers或數(shù)據(jù)依賴共同使用
Data dependency barriers(數(shù)據(jù)依賴屏障)
2.1 數(shù)據(jù)依賴是一種弱讀屏障,用于確保后一個(gè)依賴前一個(gè)操作結(jié)果的操作正確執(zhí)行又碌。如:
*A = 5九昧;
X= *D;
可能的內(nèi)存順序是:
1)STORE *A = 5,x = LOAD *D
2)x = LOAD *D毕匀,STORE *A = 5
而第二種情況將產(chǎn)生錯(cuò)誤铸鹰,因?yàn)樗茸x取寄存器地址再設(shè)置寄存器地址值。(從而導(dǎo)致使用了舊的地址值)
2.2 如果一個(gè)load操作獲取到存儲(chǔ)在另一個(gè)CPU里的指令列表皂岔,那么之道該屏障執(zhí)行完成蹋笼,該序列中所有先于barriers的stores操作對于數(shù)據(jù)依賴屏障之后的任意loads操作都是可見的。
read(或load)memory barriers
3.1 讀內(nèi)存屏障是在數(shù)據(jù)依賴屏障上加上一個(gè)管理躁垛。所有在屏障之前的loads操作將先于屏障之后的loads操作發(fā)生剖毯。
3.2 讀內(nèi)存屏障包含數(shù)據(jù)依賴屏障,因而其包含數(shù)據(jù)依賴的功能教馆。
3.3 讀內(nèi)存屏障通常配合寫內(nèi)存屏障使用
General memory barriers
通用的內(nèi)存屏障確保了所有在屏障之前的LOAD和STORE操作將先于所有位于屏障之后的LOAD和STORE操作發(fā)生逊谋。
一對隱含變量:
Acquire:
Acquire之后的所有內(nèi)存操作將發(fā)生在Acquire操作之后。
Acquire操作包含lock操作土铺,smp_load_acquire胶滋、smp_cond_load_acquire操作。
發(fā)生在Acquire操作之前的內(nèi)存操作可能會(huì)在Acquire完成之后發(fā)生悲敷。
一個(gè)Acquire操作應(yīng)當(dāng)配合Release操作究恤。
一個(gè)給定變量Acquire之后,在其上的所有先于Release的操作將確保已完成后德。
Release操作:
Release確保所有先于Release的內(nèi)存操作將先于Release操作發(fā)生部宿。
Release操作包含unlock和smp_store_release。
Release操作之后的內(nèi)存操作可能會(huì)先于Release操作發(fā)生瓢湃。
Reference:
https://www.zhihu.com/question/24301047/answer/85844428
https://zhuanlan.zhihu.com/p/45566448
https://github.com/torvalds/linux/blob/master/Documentation/memory-barriers.txt