Erlang運行時中的原子操作


Erlang運行時提供的原子操作API

盡管Erlang給開發人員提供的語義是基於消息傳遞式的同步,對於應用開發者來說,使用這種語義可以避免使用鎖;但是在Erlang運行時(ERTS)中,為了充分利用多核處理器中多個處理器核心,Erlang運行時采用了多線程的結構,例如一個調度器就運行在一個線程中,因此Erlang運行時本身也是一個多線程應用程序。目前大部分多核處理器都采用共享內存的架構共享數據,因此這些線程之間通信的最高效方式就是通過共享內存進行通信。多個線程對共享內存的訪問必須進行同步,否則就可能會造成數據損壞。和其他大多數多線程應用程序一樣,ERTS也采用鎖機制(自旋鎖、互斥鎖、讀寫鎖和條件變量)進行同步。

當一個需要對共享數據進行修改之前首先獲得鎖,此時其他線程必須等待獲得鎖,當線程修改完數據之后再釋放鎖。修改共享數據的部分稱為臨界區。臨界區的復雜程度決定了這個鎖的粒度。鎖的粒度越小說明其他線程等待的時間也越少。在所有同步機制中,粒度最小的就是原子操作了。原子操作通常是對一個變量(計數器)進行簡單的操作,例如讀取、設置以及簡單的加減運算等。原子操作是所有同步機制的根本,因為同步機制本質上都是基於多個線程對於同一個變量的讀取和操作,如果“讀取和操作”這兩個步驟會被其他線程打斷,那么就無法保證這個變量的完整性。

原子操作是如此的重要,因此現代的計算機體系一般都直接通過硬件支持原子操作。例如x86平台提供的lock指令前綴,lock前綴可以用於一些“讀取並修改寫入”的指令。這一類指令涉及到讀取-計算-寫入一個序列的操作。當指令前面加上了lock前綴時,CPU會在執行指令的時候通過某種方法禁止其他指令對涉及到的內存地址進行訪問(例如鎖住總線),從而實現了指令能夠原子地完成這個操作序列。

ERTS的基礎庫提供了非常全面的原子操作,在x86平台上這些原子操作都直接映射到底層的硬件實現。在R15中,根據操作數的長度,ERTS提供的原子操作API以以下形式表示:

  • 32位和機器字長:ethr_atomic[32]_<OP>[_<BARRIER>],帶有32表示32位的原子操作。
  • 雙字長:ethr_dw_atomic_<OP>[_<BARRIER>]

ethr的前綴表示這是ERTS中屬於ethread線程庫的API。<OP>表示具體操作的名稱。32位變量和機器字長的變量自持以下原子操作:

  • cmpxchg:接受一個新的值val和一個老的值old_val,如果原始值等於old_val,則將原始值更新為新值val並返回老的值;如果不等於,則返回原始值。
  • xchg:接受一個新的值val,將原始值更新為val,然后返回原始值的老值。
  • set:接受一個新的值val,將原始值更新為val,不返回值。
  • init:初始化,同set。
  • add_read:接受一個值val,將原始值更新為原始值加上val的值,返回得到的結果。
  • read:返回原始值。
  • inc_read:原始值自增1,返回新的值。
  • dec_read:原始值自減1,返回新的值。
  • add:接受一個值val,將原始值更新為原始值加上val的值,不返回結果。
  • inc:原始值自增1,不返回結果。
  • dec:原始值自減1,不返回結果。
  • read_band:接受一個值val,將原始值更新為原始值和val按位與的結果,返回原始值的老值。
  • read_bor:接受一個值val,將原始值更新為原始值和val按位或的結果,返回原始值的老值。

例如ethr_atomic_cmpxchg(ethr_atomic_t *var, ethr_sint_t val, ethr_sint_t old_val)這個api表示操作數寬度為機器字長,在一個原子操作內:將var指向的原子變量的值和old_val的值進行比較,如果相等,則將var指向的原子值替換為val,並返回old_val的值。如果不相等,則返回var指向的原子變量的原始值。因此,在使用這個api的時候,可以將返回值和old_var進行比較,如果等於的話則說明替換操作成功了,否則說明替換操作失敗了。cmpxchg是“compare-and-set(CAS)”類的操作,在同步機制的實現中經常會使用到這條指令。

那么這些原子操作后面的[_<BARRIER>]是什么?在Erlang虛擬機的源代碼中,有很多調用原子操作api的代碼,但是每一個調用后面都會帶一個像_nob或_mb這樣的尾巴,這是什么意思呢?下一節解釋這個尾巴的意思。

為什么要使用內存屏障

ERTS原子API后面的[_<BARRIER>]尾巴表示這個原子操作API附帶的內存屏障(memory barrier)語義。多線程編程的困難在於不僅要非常小心多線程之間數據的同步,還要關注一些編譯器和硬件的細節。為了提升代碼的執行性能,編譯器和處理器有可能會對指令的執行順序進行調整,例如為了提升性能將一些寫入操作合並在一起執行。對於單個線程來說,編譯器和處理器的亂序處理可以保證單個線程看到的是和原始代碼一致的結果。但是在多線程程序中,某一個線程可能需要觀察另一個線程操作的值,所以編譯器和處理器的亂序處理可能會導致線程讀到的值是舊的值。例如下面這個例子:

線程1 線程2

Store(c1,0)

r1=Load(c2)

store(c2,0)

r2=Load(c1)

 

 

 

 

c1和c2的初始值為1,線程1和線程2分別在c1和c2寫入0,然后分別讀取c2和c1。

假定這個程序如果完全按照所示的順序執行,而且Load操作和Store操作是原子操作,那么執行結束之后r1和r2的值會出現以下幾種情況:

r1 r2
0 0
0 1
1 0

 

 

 

 

也就是說不可能出現r1和r2同時為1的情況。但是在真實硬件上,兩個線程的兩條指令都可能會顛倒執行,因此導致最終得到的r1和r2同時為1。但是對於單個的線程來說,這兩條指令完全是可以顛倒的,因為對於單個線程來說,c1和c2是獨立的,所以兩條指令交換順序執行在單個線程看來是沒有區別的。在多線程編程中,如果多個線程通過一個變量進行同步,那么這種亂序會導致失敗的同步。例如下面這個例子,線程1執行的代碼如下:

1 while (f == 0);
2 print x;

 

線程2執行的代碼如下:

1 x = 42;
2 f = 1;

 

兩個線程通過變量f進行同步,變量f的初始值為0。當線程2將f設置為1之后,線程1退出while循環,應該打印出x的值為42。但是由於線程2的兩個寫入指令可能會顛倒執行,導致在設置x之前就設置了f,所以線程1可能會打印出x的舊值。此外,在線程1中,while循環要先讀取f的值再檢查f的值,因此也有可能會因為亂序執行而導致循環提前退出。為了讓程序能夠按照代碼的意思執行,需要一種機制能夠強制處理器按照程序所需要的順序執行代碼。這種機制就是內存屏障。根據上面的描述,在編譯器層次和處理器層次都需要內存屏障。要注意,編譯器層次的內存屏障不能保證在處理器上的執行順序,因為編譯器的內存屏障只是對編譯器優化技術的約束,處理器依然有可能會亂序執行。

從寫入數據和讀取數據的順序角度,內存屏障可以總結為以下4類:

LoadLoad屏障

這一類屏障處理 Load1; LoadLoad; Load2; 的指令順序。在Load1和Load2之間插入LoadLoad屏障可以保證Load2加載數據之前Load1的數據已經加載完成。

StoreStore屏障

這一類屏障處理 Store1; StoreStore; Store2; 的指令順序。在Store1和Store2之間插入StoreStore屏障可以保證Store2以及后續指令在執行的時候所有的處理器都可以看到Store1寫入的數據。

LoadStore屏障

這一類屏障處理 Load1; LoadStore; Store2; 的指令順序。在Load1和Store2之間插入LoadStore屏障可以保證Store2以及后續指令在執行的時候Load1加載的數據已經加載完成。

StoreLoad屏障

這一類屏障處理 Store1; StoreLoad; Load2; 的指令順序。在Store1和Load2之間插入StoreLoad屏障可以保證Load2以及后續指令加載數據之前Store1寫入的數據已經可以被所有的處理器看到。

由於內存屏障干擾了處理器優化的執行順序,所以必然會對執行的性能帶來一定的開銷,而不同的屏障的開銷也是不同的。一般來說,越強的屏障開銷越大。除了上述4種屏障之外,還有一種比LoadLoad屏障更弱的屏障,那就是"數據依賴屏障",這種屏障只有在Load2需要Load1的結果的時候才會強制順序。

這4類屏障有意思指出在於,可以自由組合形成不同語義的屏障。這里提到的這些內存屏障都只是從語義的角度說的,具體的硬件平台不一定需要或提供了每一種屏障。內存模型越嚴格(strict)的平台所需的屏障越少,越松弛(relaxed)平台所需要的屏障也越多。

下面對ERTS中支持的屏障類型進行解釋,也就是[_<BARRIER>]可以取的值。為了支持盡可能多的硬件平台,ERTS支持的內存屏障語義很全面。為了簡潔,ERTS將原子操作和內存屏障整合在一套API中,所以ERTS中的原子操作API也兼具了內存屏障的功能。下面是ERTS支持的6種類型的內存屏障:

  • mb:完整內存屏障。以原子操作為界,之前的所有加載和寫入操作,以及之后的所有加載和寫入操作,都不能跨越原子操作的界線。完整的內存屏障可以由LoadLoad、LoadStore、StoreStore和StoreLoad四個屏障的組合實現。
  • relb:釋放屏障(release barrier)。以原子操作為界,*之前*的所有加載和寫入操作都不能跨越原子操作的界線。釋放屏障名字來源於鎖釋放操作。也就是說,鎖在釋放的時候,臨界區內的操作都要完成,不能跨越釋放屏障,否則其他線程獲得鎖之后就會破壞臨界區的數據了。根據原子操作的不同,這個屏障的實現方式也不同。如果原子操作是寫入操作(寫入釋放屏障比較符合釋放屏障名字的來源,因為鎖釋放操作通常是占有鎖的線程寫入一個變量表示鎖已經釋放了),那么可以在寫入的原子操作前面放置一個LoadStore和StoreStore的組合。如果原子操作是加載操作,那么可以在加載的原子操作之前放置一個LoadLoad和StoreLoad的組合。
  • acqb:獲得屏障(acquire barrier)。以原子操作為界,*之后*的所有加載和寫入操作都不能跨越原子操作的界線。獲得屏障的名字來源於獲得鎖的操作。也就是說,在獲得鎖之后的操作不能超越到獲得鎖之前,否則就會在還沒有獲得鎖的時候操作臨界區,導致數據被破壞。如果原子操作是加載操作(加載獲得屏障比較符合這個名字的來源,因為獲得鎖的時候通常都是讀取一個表示是否鎖定的共享變量),那么可以在加載的原子操作之后放置一個LoadStore和LoadLoad的組合。如果原子操作是寫操作,那么可以在寫入的原子操作之后放置一個StoreLoad和StoreStore的組合。
  • wb:寫屏障。以原子操作為界,之前的所有寫入操作,以及之后的所有寫入操作,都不能跨越原子操作的界線。僅針對寫操作。這個屏障剛好是StoreStore屏障。
  • rb:讀屏障。以原子操作為界,之前的所有加載操作,以及之后的所有加載操作,都不能跨越原子操作的界線。僅針對加載操作。這個屏障剛好是LoadLoad屏障。
  • ddrb:數據依賴讀屏障。表示只要確保有依賴關系的加載操作。這個屏障的實現取決於具體的編譯器和硬件平台。

結合上一節描述的原子操作,ERTS中支持所有原子操作和所有類型屏障的組合,當然有一些組合是沒有意義的。

此外,ERTS也單獨提供了內存屏障的操作,可以實現LoadLoad、LoadStore、StoreStore和StoreLoad四種屏障及其任意組合。

X86處理器的亂序執行規則

雖然上面提到了好幾種內存屏障,但是在X86/X86_64平台上, 內存屏障其實要簡單得多,因為X86平台是一個內存排序模型嚴格的平台,所以要求內存屏障的地方並不多。粗略地說,基本上可以認為在大部分情況下在X86平台上只需要StoreLoad屏障。Intel 64 and IA-32 Architectures Software Developer's Manual第三卷第一冊8.2.2列出了Intel平台上的內存重排規則,下面總結一下。對於單個處理器(也就是多核處理器中的一個核心,也就是超線程技術中的一個硬件線程),重排規則如下:

  • 讀不和讀重排;
  • 寫不和之前的讀重排;
  • 寫不重排,除了CLFLUSH執行的寫、非臨時的mov類指令(MOVNTI、MOVNTQ、MOVNTDQ、MOVNTPS和MOVNTPD)的寫以及string操作;
  • 讀有可能和之前的寫重排,但是如果對同一個地址操作則不重排;
  • 讀寫操作不和I/O指令、lock前綴的指令和串行指令重排;
  • LFENCE和MFENCE指令之后的讀操作不能跨越到指令之前;
  • LFENCE、SFENCE和MFENCE指令之后的寫操作不能跨越到指令之前;
  • LFENCE之前的讀操作不能跨越到指令之后;
  • SFENCE之前的寫操作不能跨越到指令之后;
  • MFENCE之前的讀寫操作都不能跨越到指令之后。

上面這些規則是抄手冊的。簡單地說:X86提供了LFENCE、SFENCE和MFENCE指令,分別起到LoadLoad、StoreStore和完整屏障的作用。讀不會重排,寫也只有在使用一些SSE2指令的時候會重排。只會發生StoreLoad重排的情況。因此在寫入操作和讀取操作之間要加上一個MFENCE指令。如果使用了會發生寫重排的SSE2指令,那么在需要保證順序的時候要插入SFENCE指令。

此外,lock指令也能起到內存屏障的作用。由於LFENCE、SFENCE和MFENCE指令都是SSE2指令,所以在不支持SSE2的平台上,可以隨便拿一條支持lock前綴的指令來當屏障使用。

RTFC

下面簡單分析一下ERTS中原子操作API相關的代碼。erts目錄的大致結構如下所示:

erts/
|-- emulator
|   |-- beam
|   |-- drivers
|   |-- hipe
|   |-- internal_doc
|   |-- pcre
|   |-- sys
|   |-- utils
|   |-- valgrind
|   `-- zlib
|-- epmd
|-- etc
|-- include
|   |-- erl_fixed_size_int_types.h
|   |-- erl_int_sizes_config.h.in
|   |-- erl_memory_trace_parser.h
|   `-- internal
|       |-- erl_errno.h
|       |-- erl_memory_trace_protocol.h
|       |-- erl_misc_utils.h
|       |-- erl_printf.h
|       |-- erl_printf_format.h
|       |-- erts_internal.mk.in
|       |-- ethr_atomics.h
|       |-- ethr_internal.h
|       |-- ethr_mutex.h
|       |-- ethr_optimized_fallbacks.h
|       |-- ethread.h
|       |-- ethread.mk.in
|       |-- ethread_header_config.h.in
|       |-- gcc
|       |-- i386
|       |   |-- atomic.h
|       |   |-- ethr_dw_atomic.h
|       |   |-- ethr_membar.h
|       |   |-- ethread.h
|       |   |-- rwlock.h
|       |   `-- spinlock.h
|       |-- libatomic_ops
|       |-- ppc32
|       |-- pthread
|       |   `-- ethr_event.h
|       |-- sparc32
|       |-- sparc64
|       |-- tile
|       |-- win
|       `-- x86_64
|           `-- ethread.h
|-- lib_src
|   |-- common
|   |   |-- erl_memory_trace_parser.c
|   |   |-- erl_misc_utils.c
|   |   |-- erl_printf.c
|   |   |-- erl_printf_format.c
|   |   |-- ethr_atomics.c
|   |   |-- ethr_aux.c
|   |   |-- ethr_cbf.c
|   |   `-- ethr_mutex.c
|   |-- pthread
|   |   |-- ethr_event.c
|   |   |-- ethr_x86_sse2_asm.c
|   |   `-- ethread.c
|   |-- utils
|   |   `-- make_atomics_api
|   `-- win
|-- man
|-- preloaded
|-- start_scripts

 

ERTS包括虛擬機和支撐庫。erts/emulator/beam目錄是beam虛擬機的代碼,erts/emulator/下的其他目錄是支撐虛擬機所需的其他組件代碼。erts目錄下面的include目錄和lib_src目錄包含了支撐庫的頭文件和源代碼。erts/includeinternel/中包含了支持的不同架構的特定代碼的目錄,例如i386和x86_64。以erts/include/internel/i386目錄為例,這下面包含的文件:

  • atomic.h:原生的原子操作
  • ethr_dw_atomic.h:原生的雙字節原子操作
  • ethr_membar.h:原生內存屏障相關的原語
  • ethread.h:包含其他頭文件
  • rwlock.h:簡單的讀寫鎖
  • spinlock.h:簡單的自旋鎖

erts/include/internel/i386/atomic.h頭文件中包含的是i386/x86_64平台能支持的所有原生的原子操作,如果支持某個原生的原子操作,則#define相關的宏,並且定義執行原生操作的內聯函數。例如下面這段代碼:

 1 #if ETHR_INCLUDE_ATOMIC_IMPL__ == 4
 2 #  define ETHR_HAVE_ETHR_NATIVE_ATOMIC32_CMPXCHG_MB 1
 3 #else
 4 #  define ETHR_HAVE_ETHR_NATIVE_ATOMIC64_CMPXCHG_MB 1
 5 #endif
 6 
 7 static ETHR_INLINE ETHR_AINT_T__
 8 ETHR_NATMC_FUNC__(cmpxchg_mb)(ETHR_ATMC_T__ *var,
 9                   ETHR_AINT_T__ new,
10                   ETHR_AINT_T__ old)
11 {
12     __asm__ __volatile__(
13       "lock; cmpxchg" ETHR_AINT_SUFFIX__ " %2, %3"
14       : "=a"(old), "=m"(var->counter)
15       : "r"(new), "m"(var->counter), "0"(old)
16       : "cc", "memory"); /* full memory clobber to make this a compiler barrier */
17     return old;
18 }

 

在這里,如果是x86_64平台,則ETHR_INCLUDE_ATOMIC_IMPL__定義為8,所以這段代碼定義了ETHR_HAVE_ETHR_NATIVE_ATOMIC64_CMPXCHG_MB,說明在這個硬件平台上提供了原生的64位原子的CMPXCHG操作,而且這個原子操作支持原生的完全內存屏障MB。這個函數定義為內聯函數並且內嵌匯編。第13行可以看到使用了帶有lock前綴的cmpxchg指令。第16行的“cc”約束表示cmpxchg指令會影響標志寄存器。這里重要的是“memory”約束,這個約束實際上告訴編譯器這里要有一個編譯器屏障。要求硬件屏障之前必須要求編譯器屏障,否則連程序順序都不對了還談什么執行順序。

erts/include/internel/i386/atomic.h頭文件中的其他定義也類似,總結下來,在x86_64平台下定義了以下原生原子操作:

 1 ETHR_HAVE_ETHR_NATIVE_ATOMIC64_ADDR
 2 ETHR_HAVE_ETHR_NATIVE_ATOMIC64_CMPXCHG_MB
 3 ETHR_HAVE_ETHR_NATIVE_ATOMIC64_XCHG_MB
 4 ETHR_HAVE_ETHR_NATIVE_ATOMIC64_SET
 5 ETHR_HAVE_ETHR_NATIVE_ATOMIC64_SET_RELB
 6 ETHR_HAVE_ETHR_NATIVE_ATOMIC64_SET_MB
 7 ETHR_HAVE_ETHR_NATIVE_ATOMIC64_READ
 8 ETHR_HAVE_ETHR_NATIVE_ATOMIC64_ADD_MB
 9 ETHR_HAVE_ETHR_NATIVE_ATOMIC64_INC_MB
10 ETHR_HAVE_ETHR_NATIVE_ATOMIC64_DEC_MB
11 ETHR_HAVE_ETHR_NATIVE_ATOMIC64_ADD_RETURN_MB

 

 可以看出,這些原生的原子操作已經可以支持ERTS所需要的所有原子操作了。為什么這些原子操作基本上都是帶有MB(即完全內存屏障)的?因為這些操作的指令前面都加了lock前綴,而在x86平台上加了lock前綴的指令自動成了完全內存屏障。也就是說,在x86平台上,這些帶MB的原生操作只能是最強的內存屏障了:完全屏障。不過好消息是,讀操作READ和寫操作SET都有不帶任何屏障的版本,所以可以幫助實現“獲得屏障”和“釋放屏障”。

erts/include/internel/i386/ethr_membar.h頭文件定義了原生的內存屏障操作。這個文件包含了x86平台的mfence、lfence和sfence指令,並且將LoadLoad、LoadStore、StoreLoad和StoreStore屏障映射到這些指令。這個頭文件假定在ERTS中只會涉及到StoreLoad的情況,所以定義了以下宏:

1 #define ETHR_MEMBAR(B) \
2   ETHR_CHOOSE_EXPR((B) & ETHR_StoreLoad, ethr_mfence__(), ethr_cfence__())

 

B可以是ETHR_LoadLoad、ETHR_LoadStore、ETHR_StoreLoad和ETHR_StoreStore,分別表示4種屏障。但是在這個宏中,只有ETHR_StoreLoad才會起作用,在x86平台中用mfence表示這個屏障,其他屏障都只是轉換為簡單的編譯器屏障即可。順便提一下ethr_cfence__()是一個靜態內聯函數,代碼如下:

1 static __inline__ void
2 ethr_cfence__(void)
3 {
4     __asm__ __volatile__ ("" : : : "memory");
5 }

 

這個函數內嵌了一條空匯編代碼,但是告訴編譯器“memory”約束,告訴編譯器說這段代碼會修改內存,所以編譯器必然要將這段代碼之前的內存訪問提交,也就達到了編譯器屏障的效果。這種寫法是GCC編譯器屏障的慣用寫法。

從i386目錄向上,來到erts/include/internel目錄。erts/include/internel/ethr_atomics.h文件包含了更高層一般性的定義,將所有原子操作和內存屏障語義的組合映射到了每一個平台的原生原子操作。拿機器字長的cmpxchg帶wb內存屏障的操作舉例:

 1 static ETHR_INLINE ethr_sint_t ETHR_ATMC_FUNC__(cmpxchg_wb)(ethr_atomic_t *var, ethr_sint_t val, ethr_sint_t old_val)
 2 {
 3     ethr_sint_t res;
 4 #if defined(ETHR_HAVE_NATMC_CMPXCHG_WB)
 5     res = (ethr_sint_t) ETHR_NATMC_FUNC__(cmpxchg_wb)(var, (ETHR_NAINT_T__) val, (ETHR_NAINT_T__) old_val);
 6 #elif defined(ETHR_HAVE_NATMC_CMPXCHG)
 7     ETHR_MEMBAR(ETHR_StoreStore);
 8     res = (ethr_sint_t) ETHR_NATMC_FUNC__(cmpxchg)(var, (ETHR_NAINT_T__) val, (ETHR_NAINT_T__) old_val);
 9 #elif defined(ETHR_HAVE_NATMC_CMPXCHG_MB)
10     res = (ethr_sint_t) ETHR_NATMC_FUNC__(cmpxchg_mb)(var, (ETHR_NAINT_T__) val, (ETHR_NAINT_T__) old_val);
11 #elif defined(ETHR_HAVE_NATMC_CMPXCHG_RB)
12     ETHR_MEMBAR(ETHR_StoreStore);
13     res = (ethr_sint_t) ETHR_NATMC_FUNC__(cmpxchg_rb)(var, (ETHR_NAINT_T__) val, (ETHR_NAINT_T__) old_val);
14 #elif defined(ETHR_HAVE_NATMC_CMPXCHG_ACQB)
15     ETHR_MEMBAR(ETHR_StoreStore);
16     res = (ethr_sint_t) ETHR_NATMC_FUNC__(cmpxchg_acqb)(var, (ETHR_NAINT_T__) val, (ETHR_NAINT_T__) old_val);
17 #elif defined(ETHR_HAVE_NATMC_CMPXCHG_RELB)
18     ETHR_MEMBAR(ETHR_StoreStore);
19     res = (ethr_sint_t) ETHR_NATMC_FUNC__(cmpxchg_relb)(var, (ETHR_NAINT_T__) val, (ETHR_NAINT_T__) old_val);
20 #else
21 #error "Missing implementation of ethr_atomic_cmpxchg_wb()!"
22 #endif
23     return res;
24 }

 

這段代碼由一組#if指令組成。ETHR_ATMC_FUNC__(cmpxchg_wb)會展開為這個原子操作的名字,不過是內部使用的名字,erts/lib_src/ethr_atomics.c中的包裝函數會調用這個名字。這段函數處理了各種原生實現缺胳膊少腿的情況。例如如果原生平台只提供了不帶內存屏障的原子操作,也就是ETHR_HAVE_NATMC_CMPXCHG條件為真,那么這個函數會添加一個內存屏障。由於我們的x86平台的原生操作提供了原生的完全內存屏障,所以功能比寫屏障要強,所以直接調用帶_mb的版本即可。這個文件中的其他原子操作函數也就是類似定義的。

下面進入erts/lib_src/ethr_atomics.c這個C語言文件,這里面都是對上一個文件的包裝。這個文件還提供了各種fallback,也就是在硬件平台沒有提供原生實現時的操作。現在估計也沒什么平台不會提供原生的原子操作了,所以這些fallback實際上意義不大。

erts/include/internel/ethr_atomics.h文件和erts/lib_src/ethr_atomics.c文件實際上是各個層次操作的映射,里面最終定義了我們所需要的所有原子操作和屏障語義的組合,這樣光是機器字長的原子操作就78個,32位的也有78個,還有一些雙字節的原子操作。如果每一個都手工定義,那Rickard Green大牛會累死的,作為geek,基本素質就是不做重復的勞動,所以他在erts/lib_src/utils目錄下放了一個程序,這個程序負責生成這兩個文件。然后在這兩個文件的頭部用詳細的注釋表示每一個硬件平台需要提供那些原生的API即可,聰明的erts/include/internel/ethr_atomics.h和erts/lib_src/ethr_atomics.c能夠根據可用的原生操作生成所有的原子API。

結和參考資料

 

本文介紹了ERTS中提供的原子操作,並且介紹了由於處理器亂序執行給多線程程序同步帶來的問題和解決方法。Paul McKenney的文章“Memory Barriers: a Hardware View for Software Hackers”從cache一致性協議和store buffer的根源解釋了發生亂序的原因,這篇文章還介紹了不同硬件平台的內存屏障。Preshing的博客“preshing on programming”介紹了很多和內存屏障相關的內容,例如內存重排現象的重現,通過版本控制系統為metaphor介紹各種類型內存屏障以及強弱內存模型等;另外這個博客還有一些關於多線程編程的內容和一些編程相關的心得,值得推薦。此外就是各個平台處理器的文檔了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM