C言語における「ATOMIC_VAR_INIT」:並行処理における共有変数の初期化


ATOMIC_VAR_INIT の役割

ATOMIC_VAR_INIT は、共有変数を適切な初期値で初期化することで、データ競合などの問題を発生させないようにします。具体的には、以下の役割を果たします。

  • コンパイラに対して、共有変数が原子操作の対象であることを明示的に通知します。
  • 複数のスレッドが同時に共有変数を初期化しようとしても、競合が発生せずに正しい値が設定されます。
  • 共有変数を初期化されていない状態から確実な値に初期化します。

ATOMIC_VAR_INIT の使用方法

ATOMIC_VAR_INIT は、以下の構文で使用されます。

atomic_type ATOMIC_VAR_INIT(initial_value);

ここで、

  • initial_value は、共有変数の初期値を表します。
  • atomic_type は、共有変数の型を表します。

int atomic_counter = ATOMIC_VAR_INIT(0);

この例では、atomic_counter という名前の共有変数が 0 で初期化されます。

ATOMIC_VAR_INIT の注意点

  • ATOMIC_VAR_INIT は、コンパイラによって異なる動作をする場合があります。詳細は、コンパイラのドキュメントを参照してください。
  • ATOMIC_VAR_INIT は、静的ストレージ期間またはスレッドローカルストレージ期間の共有変数のみを初期化することができます。
  • ATOMIC_VAR_INIT は、C11 標準で導入されましたが、C17 標準では非推奨となり、C23 標準では削除されました。

ATOMIC_VAR_INIT の代替方法

C17 標準以降では、ATOMIC_VAR_INIT の代わりに、以下の方法で共有変数を初期化することができます。

  • std::atomic_init(&variable, value) 関数を使用する。
  • std::atomic<T>::atomic(T value) コンストラクタを使用する。


#include <stdio.h>
#include <stdatomic.h>

int atomic_counter = ATOMIC_VAR_INIT(0);

int main() {
  for (int i = 0; i < 1000; ++i) {
    atomic_fetch_add(&atomic_counter, 1);
  }

  printf("atomic_counter: %d\n", atomic_counter);

  return 0;
}

このプログラムは、atomic_counter という名前の静的ストレージ期間の共有変数を 0 で初期化し、1000 回ループで atomic_fetch_add 関数を使用して共有変数の値を 1 ずつ増やします。最後に、共有変数の最終的な値を出力します。

例2:スレッドローカルストレージ期間の共有変数の初期化

#include <stdio.h>
#include <stdatomic.h>
#include <pthread.h>

void *thread_routine(void *arg) {
  atomic_int local_counter = ATOMIC_VAR_INIT(0);

  for (int i = 0; i < 1000; ++i) {
    atomic_fetch_add(&local_counter, 1);
  }

  printf("local_counter: %d\n", local_counter);

  return NULL;
}

int main() {
  pthread_t threads[4];

  for (int i = 0; i < 4; ++i) {
    pthread_create(&threads[i], NULL, thread_routine, NULL);
  }

  for (int i = 0; i < 4; ++i) {
    pthread_join(threads[i], NULL);
  }

  return 0;
}

このプログラムは、thread_routine 関数を実行する4つのスレッドを作成します。各スレッドは、local_counter という名前のスレッドローカルストレージ期間の共有変数を 0 で初期化し、1000 回ループで atomic_fetch_add 関数を使用して共有変数の値を 1 ずつ増やします。最後に、各スレッドの共有変数の最終的な値を出力します。



手動初期化

最も基本的な方法は、共有変数を明示的に初期化することです。

int shared_counter = 0;

この方法の利点は、シンプルでわかりやすいことです。欠点は、複数のスレッドから同時に初期化しようとした場合、データ競合が発生する可能性があることです。

同期機構の利用

共有変数の初期化時に同期機構を使用することで、データ競合を回避することができます。代表的な同期機構は以下の通りです。

  • 条件変数:特定の条件が満たされるまでスレッドをブロックします。
  • セマフォア:共有変数の使用回数を制御します。
  • ミューテックス:共有変数へのアクセスを排他的に制御します。

同期機構を使用する方法は、より複雑になりますが、データ競合を確実に回避することができます。

原子操作関数

C11 標準以降では、原子操作関数を使用して共有変数を安全に初期化することができます。原子操作関数は、共有変数への読み書きを単一のアトミック操作として実行するため、データ競合が発生しません。代表的な原子操作関数は以下の通りです。

  • std::atomic_init(&variable, value):共有変数を value で初期化します。
  • std::atomic<T>::atomic(T value):共有変数を value で初期化します。

原子操作関数を使用する方法は、比較的新しい方法ですが、安全で効率的な方法です。

コンパイラ固有の拡張機能

一部のコンパイラでは、共有変数の初期化専用の拡張機能を提供しています。詳細は、コンパイラのドキュメントを参照してください。

最適な方法の選択

共有変数の初期化に最適な方法は、プログラムの要件によって異なります。以下の点を考慮して、適切な方法を選択してください。

  • プログラムの移植性は重要ですか?
  • プログラムのパフォーマンスは重要ですか?
  • 複数のスレッドから同時に共有変数にアクセスする可能性がありますか?
  • 共有変数は静的ストレージ期間ですか、それともスレッドローカルストレージ期間ですか?