C言語のアトミック型修飾子「_Atomic」の使い方を教えて!
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Verse(UEFN), Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
本記事はC11規格のスレッドを理解していることを前提とします.
目次
アトミック型修飾子「_Atomic」
_Atomicは,C言語のC11規格から採用されたアトミック型修飾子です.
ここで,アトミック操作とは,途中で割り込まれない原始的(アトミック)な操作のことです.
アトミック操作は,基本的にはハードウェアの専用命令を利用して実装されます.
_Atomicはstdatomic.h内で利用されています.
stdatomic.hは/usr/include以下にはなく,/usr/lib/gcc/x86_64-linux-gnu/XX/include/stdatomic.hにあります(XXばGCCのバージョン番号).
stdatomic.hの詳細は以下を見ると良いです.
それでは,stdatomic.hで定義されている列挙やマクロ,宣言されている関数を紹介していきます.
memory_order列挙型
1 2 3 4 5 6 7 8 9 |
typedef enum { memory_order_relaxed = __ATOMIC_RELAXED, memory_order_consume = __ATOMIC_CONSUME, memory_order_acquire = __ATOMIC_ACQUIRE, memory_order_release = __ATOMIC_RELEASE, memory_order_acq_rel = __ATOMIC_ACQ_REL, memory_order_seq_cst = __ATOMIC_SEQ_CST } memory_order; |
memory_order列挙型は,通常の(ノンアトミックの)メモリ同期操作を指定し,操作の順序付けを提供します.
memory_orderの整数定数は以下の通りです.
- memory_order_relaxed:メモリ同期なし
- memory_order_consume:以下のmemory_order_acquireより弱い順序付けでのreadのメモリ同期あり(仕様検討中のため,一時的に非推奨)
- memory_order_acquire :readのメモリ同期あり
- memory_order_release:writeのメモリ同期あり
- memory_order_acq_rel:read/write両方のメモリ同期あり
- memory_order_seq_cst:read/write両方のメモリ同期ありとSequential Consistencyあり
_Atomicを利用したtypedefによる型定義
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
typedef _Atomic _Bool atomic_bool; 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 __CHAR16_TYPE__ atomic_char16_t; typedef _Atomic __CHAR32_TYPE__ atomic_char32_t; typedef _Atomic __WCHAR_TYPE__ atomic_wchar_t; typedef _Atomic __INT_LEAST8_TYPE__ atomic_int_least8_t; typedef _Atomic __UINT_LEAST8_TYPE__ atomic_uint_least8_t; typedef _Atomic __INT_LEAST16_TYPE__ atomic_int_least16_t; typedef _Atomic __UINT_LEAST16_TYPE__ atomic_uint_least16_t; typedef _Atomic __INT_LEAST32_TYPE__ atomic_int_least32_t; typedef _Atomic __UINT_LEAST32_TYPE__ atomic_uint_least32_t; typedef _Atomic __INT_LEAST64_TYPE__ atomic_int_least64_t; typedef _Atomic __UINT_LEAST64_TYPE__ atomic_uint_least64_t; typedef _Atomic __INT_FAST8_TYPE__ atomic_int_fast8_t; typedef _Atomic __UINT_FAST8_TYPE__ atomic_uint_fast8_t; typedef _Atomic __INT_FAST16_TYPE__ atomic_int_fast16_t; typedef _Atomic __UINT_FAST16_TYPE__ atomic_uint_fast16_t; typedef _Atomic __INT_FAST32_TYPE__ atomic_int_fast32_t; typedef _Atomic __UINT_FAST32_TYPE__ atomic_uint_fast32_t; typedef _Atomic __INT_FAST64_TYPE__ atomic_int_fast64_t; typedef _Atomic __UINT_FAST64_TYPE__ atomic_uint_fast64_t; typedef _Atomic __INTPTR_TYPE__ atomic_intptr_t; typedef _Atomic __UINTPTR_TYPE__ atomic_uintptr_t; typedef _Atomic __SIZE_TYPE__ atomic_size_t; typedef _Atomic __PTRDIFF_TYPE__ atomic_ptrdiff_t; typedef _Atomic __INTMAX_TYPE__ atomic_intmax_t; typedef _Atomic __UINTMAX_TYPE__ atomic_uintmax_t; |
上記に_Atomicを利用したtypedefによる型定義を示します.
_Atomicは上記の型で利用します.
ATOMIC_VAR_INITマクロ
1 |
ATOMIC_VAR_INIT(value) |
ATOMIC_VAR_INITマクロは,valueと初期化互換性のある型のアトミックオブジェクトの初期化に適したトークン列に展開されます.
例えば,atomic_int型では,以下のように利用します.
1 |
atomic_int guide = ATOMIC_VAR_INIT(123); |
atomic_initマクロ
1 |
void atomic_init(volatile A *obj, C value); |
atomic_initマクロは,objが指すアトミックオブジェクトをvalueに初期化します.
Aがロックされたアトミック型の場合,objに関連するロックもatomic_initマクロの呼び出しの後に初期化されます.
kill_dependencyマクロ
1 |
kill_dependency(y) |
kill_dependencyマクロは,yの値を返し,変数yの依存関係の連鎖を終了させます.
読み込んだ値に依存する式に対する順序を保証するmemory_order_consumeのメモリ順序において,値の依存性を断ち切り,最適化を許可します.
atomic_thread_fence関数
1 |
extern void atomic_thread_fence(memory_order); |
atomic_thread_fence関数は,アトミック操作に対する補完的なメモリフェンスを提供します.
詳細はC++ですが,こちらを読みましょう!
atomic_signal_fence関数
1 |
extern void atomic_signal_fence(memory_order); |
atomic_signal_fence関数は,同一スレッド内のシグナルハンドラ実行との間でのみ有効なメモリフェンスを発行します.
詳細はC++ですが,こちらを読みましょう!
atomic_is_lock_freeマクロ
1 |
_Bool atomic_is_lock_free(const volatile A *obj); |
atomic_is_lock_freeマクロは,objが指すオブジェクトがロックフリーであるかどうかを返します.
ATOMIC_FREEマクロ
1 2 3 4 5 6 7 8 9 10 |
#define ATOMIC_BOOL_LOCK_FREE __GCC_ATOMIC_BOOL_LOCK_FREE #define ATOMIC_CHAR_LOCK_FREE __GCC_ATOMIC_CHAR_LOCK_FREE #define ATOMIC_CHAR16_T_LOCK_FREE __GCC_ATOMIC_CHAR16_T_LOCK_FREE #define ATOMIC_CHAR32_T_LOCK_FREE __GCC_ATOMIC_CHAR32_T_LOCK_FREE #define ATOMIC_WCHAR_T_LOCK_FREE __GCC_ATOMIC_WCHAR_T_LOCK_FREE #define ATOMIC_SHORT_LOCK_FREE __GCC_ATOMIC_SHORT_LOCK_FREE #define ATOMIC_INT_LOCK_FREE __GCC_ATOMIC_INT_LOCK_FREE #define ATOMIC_LONG_LOCK_FREE __GCC_ATOMIC_LONG_LOCK_FREE #define ATOMIC_LLONG_LOCK_FREE __GCC_ATOMIC_LLONG_LOCK_FREE #define ATOMIC_POINTER_LOCK_FREE __GCC_ATOMIC_POINTER_LOCK_FREE |
ATOMIC_FREEマクロは,対応するアトミック型のロックフリー性を示します.
非ポインタ型については,符号付き,符号無しの両方に適用されます.
atomic_flag構造体/ATOMIC_FLAG_INITマクロ
1 2 3 4 5 6 7 8 |
typedef _Atomic struct { #if __GCC_ATOMIC_TEST_AND_SET_TRUEVAL == 1 _Bool __val; #else unsigned char __val; #endif } atomic_flag; |
atomic_flag構造体は,アトミックなフラグを管理するものです.
__GCC_ATOMIC_TEST_AND_SET_TRUEVAL == 1の場合は_Bool型,それ以外はunsigned char型でフラグを管理します.
1 |
#define ATOMIC_FLAG_INIT { 0 } |
ATOMIC_FLAG_INITマクロは,atomic_flag構造体を0に初期化するマクロです.
_explicitマクロ
ここで,末尾が_explicitでないマクロは,引数memory_orderをmemory_order_seq_cstとした対応する_explicit関数と同じ動作になります.
_explicitマクロは,引数memory_orderの値に応じてメモリに影響を与えます.
atomic_store/atomic_store_explicitマクロ
1 2 |
void atomic_store(volatile A *object, C desired); void atomic_store_explicit(volatile A *object, C desired, memory_order order); |
atomic_store/atomic_store_explicitマクロは,objectが指す値を希望する値でアトミックに置き換えます.
order引数は,memory_order_acquire,memory_order_consume,memory_order_acq_relを設定できません.
atomic_load/atomic_load_explicitマクロ
1 2 |
C atomic_load(volatile A *object); C atomic_load_explicit(volatile A *object, memory_order order); |
atomic_load/atomic_load_explicitマクロは,objectが指す値をアトミックに返します.
order引数はmemory_order_release,memory_order_acq_relを設定できません.
atomic_exchange/atomic_exchange_explicitマクロ
1 2 |
C atomic_exchange(volatile A *object, C desired); C atomic_exchange_explicit(volatile A *object, C desired, memory_order order); |
atomic_exchange/atomic_exchange_explicitマクロは,objectが指す値を希望する値でアトミックに置き換えます.
返り値は,効果の直前にobjectが指した値です.
atomic_compare_exchange_strong/atomic_compare_exchange_strong_explicit/atomic_compare_exchange_weak/atomic_compare_exchange_weak_explicitマクロ
1 2 3 4 |
_Bool atomic_compare_exchange_strong(volatile A *object, C *expected, C desired); _Bool atomic_compare_exchange_strong_explicit(volatile A *object, C *expected, C desired, memory_order success, memory_order failure); _Bool atomic_compare_exchange_weak(volatile A *object, C *expected, C desired); _Bool atomic_compare_exchange_weak_explicit(volatile A *object, C *expected, C desired, memory_order success, memory_order failure); |
atomic_compare_exchange_strong/atomic_compare_exchange_strong_explicit/atomic_compare_exchange_weak/atomic_compare_exchange_weak_explicitマクロは,アトミックにobjectが指す値とexpectedが指す値が等しいかどうかを比較し,真ならばobjectが指す値をdesiredで置き換え,偽ならばexpectedの値をobjectが指す値で更新します.
さらに,比較が真の場合,成功の値に従ってメモリに影響を与え,比較が偽の場合,失敗の値に従ってメモリに影響を与えます.
返り値は,比較の結果です.
atomic_compare_exchange_weak/atomic_compare_exchange_weak_explicitマクロは,偽りの失敗をすることがあります.
つまり,expectedとobjectが参照するメモリの内容が等しい場合でも0を返し,元々そこにあったのと同じメモリ内容をexpectedに格納してしまうことがあります.
PowerPCやARM64などLL/SC命令を提供するアーキテクチャにおいて,atomic_compare_exchange_weak/atomic_compare_exchange_weak_explicitマクロは,比較と交換がループ内にある場合,より良いパフォーマンスをもたらします.
比較と交換でループを必要としない場合は,atomic_compare_exchange_strong/atomic_compare_exchange_strong_explicitマクロが望ましいです.
atomic_fetchマクロ
1 2 3 4 5 6 7 8 9 10 |
C atomic_fetch_add(volatile A *object, M operand); C atomic_fetch_add_explicit(volatile A *object, M operand, memory_order order); C atomic_fetch_sub(volatile A *object, M operand); C atomic_fetch_sub_explicit(volatile A *object, M operand, memory_order order); C atomic_fetch_or(volatile A *object, M operand); C atomic_fetch_or_explicit(volatile A *object, M operand, memory_order order); C atomic_fetch_xor(volatile A *object, M operand); C atomic_fetch_xor_explicit(volatile A *object, M operand, memory_order order); C atomic_fetch_and(volatile A *object, M operand); C atomic_fetch_and_explicit(volatile A *object, M operand, memory_order order); |
atomic_fetchマクロは,算術演算(+,-)とビット演算(|,^,&)を行うものです.
これらの演算はすべて,任意のアトミック整数型のオブジェクトに適用可能です.
atomic_boolには適用できないことに注意して下さい.
objectが指す値を,objectが指す値と与えられたoperandに適用された計算の結果とアトミックに置き換えます.
アトミックに,効果の直前にobjectによって指される値を返します.
atomic_flag_test_and_set/atomic_flag_test_and_set_explicitマクロ
1 2 |
extern _Bool atomic_flag_test_and_set(volatile atomic_flag *); extern _Bool atomic_flag_test_and_set_explicit(volatile atomic_flag *, memory_order); |
atomic_flag_test_and_set/atomic_flag_test_and_set_explicitマクロは,アトミックにテストしてフラグを立てます.
atomic_flag_clear/atomic_flag_clear_explicitマクロ
1 2 |
extern void atomic_flag_clear(volatile atomic_flag *); extern void atomic_flag_clear_explicit(volatile atomic_flag *, memory_order); |
atomic_flag_clear/atomic_flag_clear_explicitマクロは,アトミックにフラグをクリアします.
orderは,memory_order_consume,memory_order_acquire,memory_order_acq_relを設定できません.
_Atomicのの使い方
_Atomicの使い方は以下になります.
C11規格のスレッドで2つのスレッドを作成し,atomic_fetch_add_explicitマクロでアトミックにインクリメントします.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <stdatomic.h> #include <threads.h> #define LOOP_NUM 50000 #define NR_THREADS 2 int func_thread(void *arg) { size_t i; #ifndef NO_LOCK atomic_int *count = (atomic_int *) arg; for (i = 0; i < LOOP_NUM; i++) { atomic_fetch_add_explicit(count, 1, memory_order_seq_cst); } #else int *count = (int *) arg; for (i = 0; i < LOOP_NUM; i++) { (*count)++; } #endif return 0; } int main(void) { thrd_t thrs[NR_THREADS]; size_t i; thrd_start_t func = func_thread; #ifndef NO_LOCK atomic_int count = 0; #else int count = 0; #endif for (i = 0; i < NR_THREADS; i++) { if (thrd_create(&thrs[i], func, &count) != thrd_success) { fprintf(stderr, "Error: thrd_create()\n"); exit(1); } } for (i = 0; i < NR_THREADS; i++) { if (thrd_join(thrs[i], NULL) != thrd_success) { fprintf(stderr, "Error: thrd_join()\n"); exit(2); } } printf("count = %d\n", count); return 0; } |
実行結果は以下になります.
「count = 100000」になることがわかります.
1 2 3 |
$ gcc atomic.c $ a.out count = 100000 |
-DNO_LOCKオプションをつけてコンパイルして実行した結果は以下になります.
「count = 72510」と100000にならないことがわかります.(タイミングによっては100000になることがあります.)
1 2 3 |
$ gcc atomic.c -DNO_LOCK $ a.out count = 72510 |
まとめ
C言語のアトミック型修飾子「_Atomic」の使い方を紹介しました.
アトミック操作は奥深いので,何度も読み込んできちんと理解しましょう!
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!