C11原子操作
C11原子操作
C11原子操作API
在C11標準中,首次引入原子操作。
頭文件:stdatomic.h
標準定義了__STDC_NO_ATOMICS__宏,用來在編譯時檢測是否支持stdatomic。
同時還有一系列宏和函數用來判斷各種數據類型在當前的實現中是否支持原子操作,例如:ATOMIC_CHAR_LOCK_FREE,atomic_is_lock_free。
同時,標準定義了許多原子數據類型,例如:atomic_char,atomic_int。
初始化原子變量可以使用如下函數,但不保證原子性(當然一般也不會在多線程中進行初始化)。
- ATOMIC_VAR_INIT
- atomic_init
- ATOMIC_FLAG_INIT
操作原子變量則使用如下函數,保證原子性:
- atomic_store
- atomic_load
- atomic_exchange
- atomic_compare_exchange_strong, atomic_compare_exchange_weak
- atomic_fetch_add, atomic_fetch_sub, atomic_fetch_or, atomic_fetch_xor, atomic_fetch_and
- atomic_flag_test_and_set
- atomic_flag_clear
gcc對原子操作的支持
在C11之前,gcc對原子操作的支持是通過builtin函數實現的,即__sync前綴的函數。
在C11發布之后,gcc通過stdatomic.h提供標準接口。gcc在4.9版本之后才正式、完備的支持stdatomic,在編譯命令中加上-std=c11或-std=gnu11即可。如果是之前的版本,那只能使用builtin函數了。
使用樣例
下面的例子開啟4個線程,每個線程都對全局變量sum執行累加操作,如果不使用原子操作的話,最終輸出的sum值往往會比正確結果少:
#include <stdio.h> #include <pthread.h>int sum = 0;void *func(void *param) {for (int i = 0; i < 100000; ++i) {sum++;}return NULL; }int main() {pthread_t t1, t2, t3, t4;pthread_create(&t1, NULL, func, NULL);pthread_create(&t2, NULL, func, NULL);pthread_create(&t3, NULL, func, NULL);pthread_create(&t4, NULL, func, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t3, NULL);printf("sum = %d\n", sum);return 0; }編譯執行:
$ gcc sum1.c -o sum1 $ ./sum1 sum = 128093 $ ./sum1 sum = 276478 $ ./sum1 sum = 341649即使把定義全局變量sum的地方改為volatile int sum = 0;依然不能解決問題,原因在于volatile關鍵字使得編譯器對生成的機器代碼不做優化,每次訪問sum變量時必須訪問內存而不是硬件寄存器。雖然如此,但連續兩次訪問sum變量仍然不是原子的。而sum++生成的機器代碼會先讀取sum到寄存器,寄存器加1后,再存回內存,顯然不能保證原子性。
那要如何保證對sum++是原子性的呢?答案就是使用C11提供的原子操作。更改后的代碼如下:
#include <stdio.h> #include <pthread.h> #include <stdatomic.h>atomic_int sum = ATOMIC_VAR_INIT(0);void *func(void *param) {for (int i = 0; i < 100000; ++i) {atomic_fetch_add(&sum, 1);}return NULL; }int main() {pthread_t t1, t2, t3, t4;pthread_create(&t1, NULL, func, NULL);pthread_create(&t2, NULL, func, NULL);pthread_create(&t3, NULL, func, NULL);pthread_create(&t4, NULL, func, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);printf("sum = %d\n", atomic_load(&sum));return 0; }可以看到,這里使用atomic_int類型來定義sum變量,使用atomic_fetch_add(&sum, 1);累加sum。
編譯運行看下:
$ gcc -std=c11 sum2.c -o sum2 $ ./sum2 sum = 400000 $ ./sum2 sum = 400000 $ ./sum2 sum = 400000 $ ./sum2 sum = 400000多次運行程序sum結果都是正確的。
總結
- 上一篇: Mysql索引机制B+Tree
- 下一篇: t470键盘拆解_做工保持良好水准 Th