-
內存屏障
鎖定
內存屏障,也稱內存柵欄,內存柵障,屏障指令等, 是一類同步屏障指令,是CPU或編譯器在對內存隨機訪問的操作中的一個同步點,使得此點之前的所有讀寫操作都執行後才可以開始執行此點之後的操作。
目錄
內存屏障簡介
大多數現代計算機為了提高性能而採取亂序執行,這使得內存屏障成為必須。
語義上,內存屏障之前的所有寫操作都要寫入內存;內存屏障之後的讀操作都可以獲得同步屏障之前的寫操作的結果。因此,對於敏感的程序塊,寫操作之後、讀操作之前可以插入內存屏障。
內存屏障底層體系結構相關的原語
大多數處理器提供了內存屏障指令:
- 完全內存屏障(full memory barrier)保障了早於屏障的內存讀寫操作的結果提交到內存之後,再執行晚於屏障的讀寫操作。
- 內存讀屏障(read memory barrier)僅確保了內存讀操作;
- 內存寫屏障(write memory barrier)僅保證了內存寫操作。
內存屏障是底層原語,是內存排序的一部分,在不同體系結構下變化很大而不適合推廣。需要認真研讀硬件的手冊以確定內存屏障的辦法。x86指令集中的內存屏障指令是:
lfence (asm), void _mm_lfence (void) 讀操作屏障sfence (asm), void _mm_sfence (void)[1] 寫操作屏障mfence (asm), void _mm_mfence (void)[2] 讀寫操作屏障
常見的x86/x64,通常使用lock指令前綴加上一個空操作來實現,注意當然不能真的是nop指令,但是可以用來實現空操作的指令其實是很多的,比如Linux中採用的
addl $0, 0 (%esp)
存儲器也提供了另一套語義的內存屏障指令:
- acquire semantics: 該操作結果可利用要早於代碼中後續的所有操作的結果。
- release semantics: 該操作結果可利用要晚於代碼中之前的所有操作的結果。
- fence semantics: acquire與release兩種語義的共同有效。即該操作結果可利用要晚於代碼中之前的所有操作的結果,且該操作結果可利用要早於代碼中後續的所有操作的結果。
- acq (acquire)
- rel (release).
內存屏障Windows API的內存屏障實現
下述同步函數使用適當的屏障來確保內存有序:
- 進出臨界區(critical section)的函數
- 觸發(signaled)同步對象的函數
- 等待函數(Wait function)
內存屏障多線程編程與內存可見性
內存可見性問題,主要是高速緩存與內存的一致性問題。一個處理器上的線程修改了某數據,而在另一處理器上的線程可能仍然使用着該數據在專用cache中的老值,這就是可見性出了問題。解決辦法是令該數據為volatile屬性,或者讀該數據之前執行內存屏障。
[2]
內存屏障亂序執行與編譯器重排序優化的比較
C與C++語言中,volatile關鍵字意圖允許內存映射的I/O操作。這要求編譯器對此的數據讀寫按照程序中的先後順序執行,不能對volatile內存的讀寫重排序。因此關鍵字volatile並不保證是一個內存屏障。
對於Visual Studio 2003,編譯器保證對volatile的操作是有序的,但是不能保證處理器的亂序執行。因此,可以使用InterlockedCompareExchange或InterlockedExchange函數。
內存屏障編譯器內存屏障
編譯器會對生成的可執行代碼做一定優化,造成亂序執行甚至省略(不執行)。gcc編譯器在遇到內嵌彙編語句:
asm volatile("" ::: "memory");
將以此作為一條內存屏障,重排序內存操作。即此語句之前的各種編譯優化將不會持續到此語句之後。也可用內建的__sync_synchronize
Microsoft Visual C++的編譯器內存屏障為:
_ReadWriteBarrier() MemoryBarrier()
__memory_barrier()