C語言的原子操作

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_storeatomic_store_explicit:是原子寫入操作,將原子對象的值更換為desired的值。
obj:原子對象的指針。
desired:期望寫入的值。
order:內存模型。atomic_store帶有缺省的內存模型是:memory_order_seq_cstatomic_store_explicit可使用的內存模型有:memory_order_relaxedmemory_order_releasememory_order_seq_cst

讀取(Load operation )

C atomic_load( const volatile A* obj );
C atomic_load_explicit( const volatile A* obj, memory_order order);

atomic_loadatomic_load_explicit:是原子讀取操作,返回原子對象的值。
obj:原子對象的指針。
order:內存模型。atomic_load帶有缺省的內存模型是:memory_order_seq_cst;可使用的內存模型有:memory_order_relaxedmemory_order_acquirememory_order_consumememory_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_exchangeatomic_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_relaxedmemory_order_acquirememory_order_consumememory_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_setatomic_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_clearatomic_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 orderingmemory_order_releasememory_order_acquire往往是配對使用。如果線程A針對一個原子對象進行memory_order_releasestore操作,線程B針對這個原子對象進行memory_order_acquireload操作,那么線程A中發生在store之前的所有的內存寫入(relaxed原子的或者非原子的),都是對線程B執行load后可見的。

這種同步是建立在不同線程針對同一個原子對象做 releasing storeacquiring load操作之上的。

memory_order_consume:修飾讀取操作(load),表示在本線程中,在這個語句后并且依賴這個原子變量的讀寫操作不能重排到load這個語句的前面。所有其他線程被使用來完成store操作的變量內存的寫入,對本線程是可見的。
memory_order_consumememory_order_acquire約束更小,只影響和這個原子對象相關的內存讀寫重排。

memory_order_acq_rel:修飾讀取-修改-寫入操作,表示該操作既是一個acquire操作也是一個release操作。在當前線程中,寫入操作的前后內存讀寫都不可以重排。其他所有線程針對該原子對象的release操作的內存寫入,在本線程改變修改操作之前是可見的;對于內存的修改,其他線程在acquire該原子對象前是可見的。

memory_order_seq_cst:這個是memory_order_releasememory_order_acquirememory_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實現不同的內存序

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,885評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,312評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,993評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,667評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,410評論 6 411
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,778評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,955評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,521評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,266評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,468評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,696評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,095評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,385評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,193評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,431評論 2 378

推薦閱讀更多精彩內容