版權(quán)歸原作者所有,如有侵權(quán),請(qǐng)聯(lián)系我們

[科普中國(guó)]-內(nèi)存屏障

科學(xué)百科
原創(chuàng)
科學(xué)百科為用戶提供權(quán)威科普內(nèi)容,打造知識(shí)科普陣地
收藏

內(nèi)存屏障,也稱(chēng)內(nèi)存柵欄,內(nèi)存柵障,屏障指令等, 是一類(lèi)同步屏障指令,是CPU或編譯器在對(duì)內(nèi)存隨機(jī)訪問(wèn)的操作中的一個(gè)同步點(diǎn),使得此點(diǎn)之前的所有讀寫(xiě)操作都執(zhí)行后才可以開(kāi)始執(zhí)行此點(diǎn)之后的操作。

簡(jiǎn)介大多數(shù)現(xiàn)代計(jì)算機(jī)為了提高性能而采取亂序執(zhí)行,這使得內(nèi)存屏障成為必須。

語(yǔ)義上,內(nèi)存屏障之前的所有寫(xiě)操作都要寫(xiě)入內(nèi)存;內(nèi)存屏障之后的讀操作都可以獲得同步屏障之前的寫(xiě)操作的結(jié)果。因此,對(duì)于敏感的程序塊,寫(xiě)操作之后、讀操作之前可以插入內(nèi)存屏障。

底層體系結(jié)構(gòu)相關(guān)的原語(yǔ)大多數(shù)處理器提供了內(nèi)存屏障指令:

完全內(nèi)存屏障(full memory barrier)保障了早于屏障的內(nèi)存讀寫(xiě)操作的結(jié)果提交到內(nèi)存之后,再執(zhí)行晚于屏障的讀寫(xiě)操作。

內(nèi)存讀屏障(read memory barrier)僅確保了內(nèi)存讀操作;

內(nèi)存寫(xiě)屏障(write memory barrier)僅保證了內(nèi)存寫(xiě)操作。

內(nèi)存屏障是底層原語(yǔ),是內(nèi)存排序的一部分,在不同體系結(jié)構(gòu)下變化很大而不適合推廣。需要認(rèn)真研讀硬件的手冊(cè)以確定內(nèi)存屏障的辦法。x86指令集中的內(nèi)存屏障指令是:

lfence (asm), void _mm_lfence (void) 讀操作屏障sfence (asm), void _mm_sfence (void)[1] 寫(xiě)操作屏障mfence (asm), void _mm_mfence (void)[2] 讀寫(xiě)操作屏障常見(jiàn)的x86/x64,通常使用lock指令前綴加上一個(gè)空操作來(lái)實(shí)現(xiàn),注意當(dāng)然不能真的是nop指令,但是可以用來(lái)實(shí)現(xiàn)空操作的指令其實(shí)是很多的,比如Linux中采用的

addl $0, 0 (%esp)存儲(chǔ)器也提供了另一套語(yǔ)義的內(nèi)存屏障指令:

acquire semantics: 該操作結(jié)果可利用要早于代碼中后續(xù)的所有操作的結(jié)果。

release semantics: 該操作結(jié)果可利用要晚于代碼中之前的所有操作的結(jié)果。

fence semantics: acquire與release兩種語(yǔ)義的共同有效。即該操作結(jié)果可利用要晚于代碼中之前的所有操作的結(jié)果,且該操作結(jié)果可利用要早于代碼中后續(xù)的所有操作的結(jié)果。

Intel Itanium處理器,具有內(nèi)存屏障mf的指令,具有下述modifiers1:

acq (acquire)

rel (release).

Windows API的內(nèi)存屏障實(shí)現(xiàn)下述同步函數(shù)使用適當(dāng)?shù)钠琳蟻?lái)確保內(nèi)存有序:

進(jìn)出臨界區(qū)(critical section)的函數(shù)

觸發(fā)(signaled)同步對(duì)象的函數(shù)

等待函數(shù)(Wait function)

互鎖函數(shù)(Interlocked function)1

多線程編程與內(nèi)存可見(jiàn)性多線程程序通常使用高層程序設(shè)計(jì)語(yǔ)言中的同步原語(yǔ),如Java與.NET Framework,或者API如pthread或Windows API。因此一般不需要明確使用內(nèi)存屏障。

內(nèi)存可見(jiàn)性問(wèn)題,主要是高速緩存與內(nèi)存的一致性問(wèn)題。一個(gè)處理器上的線程修改了某數(shù)據(jù),而在另一處理器上的線程可能仍然使用著該數(shù)據(jù)在專(zhuān)用cache中的老值,這就是可見(jiàn)性出了問(wèn)題。解決辦法是令該數(shù)據(jù)為volatile屬性,或者讀該數(shù)據(jù)之前執(zhí)行內(nèi)存屏障。2

亂序執(zhí)行與編譯器重排序優(yōu)化的比較C與C++語(yǔ)言中,volatile關(guān)鍵字意圖允許內(nèi)存映射的I/O操作。這要求編譯器對(duì)此的數(shù)據(jù)讀寫(xiě)按照程序中的先后順序執(zhí)行,不能對(duì)volatile內(nèi)存的讀寫(xiě)重排序。因此關(guān)鍵字volatile并不保證是一個(gè)內(nèi)存屏障。

對(duì)于Visual Studio 2003,編譯器保證對(duì)volatile的操作是有序的,但是不能保證處理器的亂序執(zhí)行。因此,可以使用InterlockedCompareExchange或InterlockedExchange函數(shù)。

對(duì)于Visual Studio 2005及以后版本,編譯器對(duì)volatile變量的讀操作使用acquire semantics,對(duì)寫(xiě)操作使用release semantics。1

編譯器內(nèi)存屏障編譯器會(huì)對(duì)生成的可執(zhí)行代碼做一定優(yōu)化,造成亂序執(zhí)行甚至省略(不執(zhí)行)。gcc編譯器在遇到內(nèi)嵌匯編語(yǔ)句:

asm volatile("" ::: "memory");將以此作為一條內(nèi)存屏障,重排序內(nèi)存操作。即此語(yǔ)句之前的各種編譯優(yōu)化將不會(huì)持續(xù)到此語(yǔ)句之后。也可用內(nèi)建的__sync_synchronize

Microsoft Visual C++的編譯器內(nèi)存屏障為:

_ReadWriteBarrier() MemoryBarrier()Intel C++編譯器的內(nèi)存屏障為1:

__memory_barrier()本詞條內(nèi)容貢獻(xiàn)者為:

王沛 - 副教授、副研究員 - 中國(guó)科學(xué)院工程熱物理研究所