C語言原子操作是在C11(C11:標準是C語言標準的第三版,前一個標準版本是[C99]標準)引入的,定義在頭文件 <stdatomic.h>中。C++11也對原子操作進了封裝,定義在頭文件<atomic>中,這里不過多的介紹。Mac系統里有對原子操作的頭文件stdatomic.h,本文的介紹也是基于這個頭文件。
1、無鎖數據類型
以下宏定義數據類型為無鎖數據類型,這些數據類型可以使用標準原子操作函數讀取、加載、修改。
#define ATOMIC_BOOL_LOCK_FREE __CLANG_ATOMIC_BOOL_LOCK_FREE
#define ATOMIC_CHAR_LOCK_FREE __CLANG_ATOMIC_CHAR_LOCK_FREE
#define ATOMIC_CHAR16_T_LOCK_FREE __CLANG_ATOMIC_CHAR16_T_LOCK_FREE
#define ATOMIC_CHAR32_T_LOCK_FREE __CLANG_ATOMIC_CHAR32_T_LOCK_FREE
#define ATOMIC_WCHAR_T_LOCK_FREE __CLANG_ATOMIC_WCHAR_T_LOCK_FREE
#define ATOMIC_SHORT_LOCK_FREE __CLANG_ATOMIC_SHORT_LOCK_FREE
#define ATOMIC_INT_LOCK_FREE __CLANG_ATOMIC_INT_LOCK_FREE
#define ATOMIC_LONG_LOCK_FREE __CLANG_ATOMIC_LONG_LOCK_FREE
#define ATOMIC_LLONG_LOCK_FREE __CLANG_ATOMIC_LLONG_LOCK_FREE
#define ATOMIC_POINTER_LOCK_FREE __CLANG_ATOMIC_POINTER_LOCK_FREE
_Bool atomic_is_lock_free( const volatile A* obj );
使用atomic_is_lock_free
判斷原子對子對象是否是無鎖的,如果對象的所有數據類型都支持原子操作返回true。
#include <iostream>
#include <stdatomic.h>
int main(int argc, const char * argv[]) {
atomic_uint _atomic_int;
atomic_init(&_atomic_int, 1);
uint32_t _a_int = 0;
std::cout<<atomic_is_lock_free(&_atomic_int)<<std::endl;
std::cout<<atomic_is_lock_free(&_a_int)<<std::endl;
return 0;
}
2、原子操作的數據類型
以下數據類型為標準中定義的支持原子操作的數據類型:
#ifdef __cplusplus
typedef _Atomic(bool) atomic_bool;
#else
typedef _Atomic(_Bool) atomic_bool;
#endif
typedef _Atomic(char) atomic_char;
typedef _Atomic(signed char) atomic_schar;
typedef _Atomic(unsigned char) atomic_uchar;
typedef _Atomic(short) atomic_short;
typedef _Atomic(unsigned short) atomic_ushort;
typedef _Atomic(int) atomic_int;
typedef _Atomic(unsigned int) atomic_uint;
typedef _Atomic(long) atomic_long;
typedef _Atomic(unsigned long) atomic_ulong;
typedef _Atomic(long long) atomic_llong;
typedef _Atomic(unsigned long long) atomic_ullong;
typedef _Atomic(uint_least16_t) atomic_char16_t;
typedef _Atomic(uint_least32_t) atomic_char32_t;
typedef _Atomic(wchar_t) atomic_wchar_t;
typedef _Atomic(int_least8_t) atomic_int_least8_t;
typedef _Atomic(uint_least8_t) atomic_uint_least8_t;
typedef _Atomic(int_least16_t) atomic_int_least16_t;
typedef _Atomic(uint_least16_t) atomic_uint_least16_t;
typedef _Atomic(int_least32_t) atomic_int_least32_t;
typedef _Atomic(uint_least32_t) atomic_uint_least32_t;
typedef _Atomic(int_least64_t) atomic_int_least64_t;
typedef _Atomic(uint_least64_t) atomic_uint_least64_t;
typedef _Atomic(int_fast8_t) atomic_int_fast8_t;
typedef _Atomic(uint_fast8_t) atomic_uint_fast8_t;
typedef _Atomic(int_fast16_t) atomic_int_fast16_t;
typedef _Atomic(uint_fast16_t) atomic_uint_fast16_t;
typedef _Atomic(int_fast32_t) atomic_int_fast32_t;
typedef _Atomic(uint_fast32_t) atomic_uint_fast32_t;
typedef _Atomic(int_fast64_t) atomic_int_fast64_t;
typedef _Atomic(uint_fast64_t) atomic_uint_fast64_t;
typedef _Atomic(intptr_t) atomic_intptr_t;
typedef _Atomic(uintptr_t) atomic_uintptr_t;
typedef _Atomic(size_t) atomic_size_t;
typedef _Atomic(ptrdiff_t) atomic_ptrdiff_t;
typedef _Atomic(intmax_t) atomic_intmax_t;
typedef _Atomic(uintmax_t) atomic_uintmax_t;
#define ATOMIC_VAR_INIT(value) (value)
#define atomic_init __c11_atomic_init
ATOMIC_VAR_INIT
用來初始化一個新的原子對象,atomic_bool flag = ATOMIC_VAR_INIT(1);
ATOMIC_VAR_INIT已在C17中被聲明為Deprecated,不建議使用。
atomic_init
: 用來初始化一個存在的原子對象 fatomic_init(&flag, 0);
// obj: 原子對象的指針
// desired: 給定的原子對象的初始值
void atomic_init( volatile A* obj, C desired );
3、讀取、寫入操作
寫入(Store operation)
void atomic_store( volatile A* obj , C desired);
void atomic_store_explicit( volatile A* obj, C desired, memory_order order);
atomic_store和atomic_store_explicit:是原子寫入操作,將原子對象的值更換為desired的值。
obj:原子對象的指針。
desired:期望寫入的值。
order:內存模型。atomic_store帶有缺省的內存模型是:memory_order_seq_cst,atomic_store_explicit可使用的內存模型有:memory_order_relaxed,memory_order_release,memory_order_seq_cst。
讀取(Load operation )
C atomic_load( const volatile A* obj );
C atomic_load_explicit( const volatile A* obj, memory_order order);
atomic_load和atomic_load_explicit:是原子讀取操作,返回原子對象的值。
obj:原子對象的指針。
order:內存模型。atomic_load帶有缺省的內存模型是:memory_order_seq_cst;可使用的內存模型有:memory_order_relaxed,memory_order_acquire,memory_order_consume,memory_order_seq_cst*。
4、讀取-修改-寫入操作(read-modify-write operation)
原子交換
C atomic_exchange( volatile A* obj, C desired );
C atomic_exchange_explicit( volatile A* obj, C desired, memory_order order );
atomic_exchange和atomic_exchange_explicit:是原子交換操作,將desired的值寫入到原子對象,并返回之前保存的舊值。
obj:原子對象的指針。
desired:期望交換的新值。
order:內存模型,所有的內存模型都可以。
原子比較交換
_Bool atomic_compare_exchange_strong( volatile A* obj, C* expected, C desired );
_Bool atomic_compare_exchange_weak( volatile A *obj, C* expected, C desired );
_Bool atomic_compare_exchange_strong_explicit( volatile A* obj, C* expected, C desired, memory_order succ, memory_order fail);
_Bool atomic_compare_exchange_weak_explicit( volatile A *obj, C* expected, C desired, memory_order succ, memory_order fail);
atomic_compare_exchange:比較原子對象*obj
和*expected
對象,如果相等將desired的值寫入原子對象obj并返回true;否則將原子對象的值取出,然后寫入到expected,并返回false。
obj:原子對象的指針。
expected:要比較對象的指針。
desired:比較不相等,期望寫入的新值。
succ:比較相等時的read-modify-write內存模型,所有的內存模型都可以。
fail:指定比較不相等時的load操作的內存模型,可使用的內存模型有:memory_order_relaxed,memory_order_acquire,memory_order_consume,memory_order_seq_cst。
以weak修飾的版本允許出現虛假的失敗(Fail Spuriously),在循環中weak修飾的比較-交換操作可能在出現*obj != *expected
的情況(其實它們相等),weak修飾的版本可能在某些平臺上擁有更好的性能。weak版本可能需要一個循環,但是strong版本不需要。
原子的加、減、或、異或、并
// 原子加 *obj +=arg;
C atomic_fetch_add( volatile A* obj, M arg );
C atomic_fetch_add_explicit( volatile A* obj, M arg, memory_order order );
// 原子減 *obj -=arg;
C atomic_fetch_sub( volatile A* obj, M arg );
C atomic_fetch_sub_explicit( volatile A* obj, M arg, memory_order order );
// 原子或 *obj |=arg;
C atomic_fetch_or( volatile A* obj, M arg );
C atomic_fetch_or_explicit( volatile A* obj, M arg, memory_order order );
// 原子異或 *obj ^=arg;
C atomic_fetch_xor( volatile A* obj, M arg );
C atomic_fetch_xor_explicit( volatile A* obj, M arg, memory_order order );
// 原子并 *obj &=arg;
C atomic_fetch_and( volatile A* obj, M arg );
C atomic_fetch_and_explicit( volatile A* obj, M arg, memory_order order );
obj:原子對象的指針。
arg:要加上、減上、或上、異或上、并上的值。
order:內存模型,所有的內存模型都可以。
5、原子標記(atomic_flag)
atomic_flag
:無鎖原子布爾類型。
以下代碼初始化atomic_flag類型變量。
#include <stdatomic.h>
atomic_flag flag = ATOMIC_FLAG_INIT; // #define ATOMIC_FLAG_INIT /* unspecified */
_Bool atomic_flag_test_and_set( volatile atomic_flag* obj );
_Bool atomic_flag_test_and_set_explicit( volatile atomic_flag* obj, memory_order order );
atomic_flag_test_and_set和atomic_flag_test_and_set_explicit:原子的設置標記值為true,并返回原理的標記值。
void atomic_flag_clear( volatile atomic_flag* obj );
void atomic_flag_clear_explicit( volatile atomic_flag* obj, memory_order order );
atomic_flag_clear和atomic_flag_clear_explicit:原子的將標記值設置為false。
6、內存屏障
void atomic_thread_fence( memory_order order );
atomic_thread_fence用來創建多線程內存屏障,和使用原子對象的同步語義作用一樣,但是不需要原子對象。
order:內存順序,所有標記都可以。
- atomic_thread_fence(memory_order_relaxed):這個沒有作用
- atomic_thread_fence(memory_order_acquire) 和 atomic_thread_fence(memory_order_consume) :內存讀(acquire)屏障。
- atomic_thread_fence(memory_order_release) :內存寫(release)屏障。
- atomic_thread_fence(memory_order_acq_rel): 既是讀屏障 也是寫屏障,也可以叫做完全內存屏障(full memory fence),保障了早于屏障的內存讀寫操作的結果提交到內存之后,再執行晚于屏障的讀寫操作。。
- atomic_thread_fence(memory_order_seq_cst) :保證完全順序一致性的完全內存屏障,約束最強。
7、內存順序(Memory Order)
內存順序指定CPU 讀取內存數據的順序,內存的排序可能發生在編譯器編譯期間,也可能發生在 CPU 指令執行期間。
int a = 1; int b = 2;
上面的代碼經過編譯器重排后可能出現:先初始化b,再初始化a的情況。即使編譯器重排后指令是按照代碼書寫順序排列的,在運行期間CPU也可能出現重排現象:先初始化b,在初始化a。
為了提高計算機資源利用率和性能,編譯器會對代碼進行重排, CPU 也會對指令進行重新排序、延緩執行、各種緩存等等,以達到更好的執行效果。當然,任何排序都不能違背代碼本身所表達的意義,在單線程情況下,這些優化通常不會有任何問題。
但是在多線程環境下,比如無鎖(lock-free)數據結構的設計中,指令的亂序執行會造成無法預測的行為。所以我們引入了內存柵欄(Memory Barrier)這一概念來解決可能存在的并發問題。
內存柵欄 (Memory Barrier)
內存柵欄是一個令 CPU 或編譯器在內存操作上限制內存操作順序的指令,通常意味著Barrier 之前的指令一定在在 Barrier 之后的指令之前執行。
C11以枚舉的方式定義了如下內存順序:
enum memory_order {
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst
};
memory_order_relaxed:只保證當前操作的原子性,沒有同步語義,不考慮線程間的同步,對其他線程的讀寫沒有順序約束,其他線程可能讀到新值,也可能讀到舊值;同一線程內執行指令的順序可能不同。
memory_order_release: 修飾寫入操作(store),表示在本線程中,在這個語句前的讀寫操作不能重排到這個語句的后面。當前線程的所有寫入操作對其他線程就是可見的,表示當前線程對非局部變量的寫入,其他線程都是可以獲取到的。
memory_order_acquire:修飾讀取操作(load),表示在本線程中,在這個語句后的讀寫操作不能重排到這個語句的前面。所有其他線程的寫入操作,對本線程是可見的。
Release-Acquire ordering:memory_order_release和memory_order_acquire往往是配對使用。如果線程A針對一個原子對象進行memory_order_release的store操作,線程B針對這個原子對象進行memory_order_acquire的load操作,那么線程A中發生在store之前的所有的內存寫入(relaxed原子的或者非原子的),都是對線程B執行load后可見的。
這種同步是建立在不同線程針對同一個原子對象做 releasing store和 acquiring load操作之上的。
memory_order_consume:修飾讀取操作(load),表示在本線程中,在這個語句后并且依賴這個原子變量的讀寫操作不能重排到load這個語句的前面。所有其他線程被使用來完成store操作的變量內存的寫入,對本線程是可見的。
memory_order_consume比memory_order_acquire約束更小,只影響和這個原子對象相關的內存讀寫重排。
memory_order_acq_rel:修飾讀取-修改-寫入操作,表示該操作既是一個acquire操作也是一個release操作。在當前線程中,寫入操作的前后內存讀寫都不可以重排。其他所有線程針對該原子對象的release操作的內存寫入,在本線程改變修改操作之前是可見的;對于內存的修改,其他線程在acquire該原子對象前是可見的。
memory_order_seq_cst:這個是memory_order_release,memory_order_acquire和memory_order_acq_rel的綜合,約束更強。
讀取就是 acquire 語義,如果是寫入就是 release 語義,如果是讀取+寫入就是 acquire-release 語義。
同時會對所有使用此 memory order 的原子操作進行同步,所有線程看到的內存操作的順序都是一樣的,就像單個線程在執行所有線程的指令一樣。
本文參考鏈接:
1. Atomic operations library
2. 內存順序(Memory Order)
3. C++11中的內存模型下篇 - C++11支持的幾種內存模型
4. 如何理解 C++11 的六種 memory order
5. C++ memory order循序漸進(四)—— 在std::atomic_thread_fence 上應用std::memory_order實現不同的內存序