C言語におけるコンカレンシーサポート:ATOMIC_*_LOCK_FREEマクロの解説


ATOMIC_*_LOCK_FREEマクロは、以下の3つの値を返すことができます。

  • 2
    アトミック操作は常にロックフリーである
  • 1
    アトミック操作は場合によってはロックフリーである可能性がある
  • 0
    アトミック操作はロックフリーではない

マクロの使用方法

#include <atomic_ops.h>

int main() {
  atomic_int counter;
  atomic_init(&counter, 0);

  if (atomic_is_lock_free(&counter)) {
    // アトミック操作を安全に実行できる
    atomic_fetch_add(&counter, 1);
  } else {
    // 同期メカニズムを使用してアクセスする必要がある
    int value = atomic_load(&counter);
    value++;
    atomic_store(&counter, value);
  }

  return 0;
}

上記の例では、atomic_is_lock_free(&counter) マクロを使用して、counter 変数がロックフリーかどうかを確認しています。ロックフリーである場合は、atomic_fetch_add 関数を使用してカウンターを安全にインクリメントできます。そうでない場合は、atomic_loadatomic_store 関数を使用して、カウンターへのアクセスを同期する必要があります。

  • ロックフリー操作は、常に高速化を保証するものではありません。状況によっては、同期メカニズムの方が効率的な場合があります。
  • ロックフリー操作であっても、メモリバリアが必要になる場合があります。詳細は、C++ 標準ライブラリのドキュメントを参照してください。
  • ATOMIC_*_LOCK_FREE マクロは、コンパイラとプラットフォームによって異なる値を返す可能性があります。


例1:ロックフリーなカウンター

#include <atomic_ops.h>

int main() {
  atomic_int counter;
  atomic_init(&counter, 0);

  if (atomic_is_lock_free(&counter)) {
    // アトミック操作を安全に実行できる
    for (int i = 0; i < 1000000; i++) {
      atomic_fetch_add(&counter, 1);
    }
  } else {
    // 同期メカニズムを使用してアクセスする必要がある
    for (int i = 0; i < 1000000; i++) {
      int value = atomic_load(&counter);
      value++;
      atomic_store(&counter, value);
    }
  }

  printf("counter: %d\n", atomic_load(&counter));

  return 0;
}

このコードは、atomic_int 型の変数 counter を作成し、0 で初期化します。その後、atomic_is_lock_free マクロを使用して counter がロックフリーかどうかを確認します。ロックフリーである場合は、atomic_fetch_add 関数を使用してカウンターを1ずつインクリメントするループを実行します。そうでない場合は、atomic_loadatomic_store 関数を使用してカウンターへのアクセスを同期するループを実行します。最後に、カウンターの値を printf 関数で出力します。

例2:ロックフリーなフラグ

#include <atomic_ops.h>

int main() {
  atomic_flag flag;
  atomic_flag_clear(&flag);

  if (atomic_is_lock_free(&flag)) {
    // アトミック操作を安全に実行できる
    while (!atomic_flag_test_and_set(&flag)) {
      // 何もしない
    }
  } else {
    // 同期メカニズムを使用してアクセスする必要がある
    while (!atomic_flag_test_and_set(&flag)) {
      int value = atomic_load(&flag);
      if (value == 0) {
        atomic_store(&flag, 1);
        break;
      }
    }
  }

  printf("flag is set\n");

  return 0;
}

このコードは、atomic_flag 型の変数 flag を作成し、クリアします。その後、atomic_is_lock_free マクロを使用して flag がロックフリーかどうかを確認します。ロックフリーである場合は、atomic_flag_test_and_set 関数を使用してフラグをセットするまでループします。フラグが既にセットされている場合は、ループを終了します。そうでない場合は、atomic_loadatomic_store 関数を使用してフラグへのアクセスを同期するループを実行します。最後に、フラグがセットされたことを printf 関数で出力します。

#include <atomic_ops.h>

struct Data {
  int value;
  atomic_flag flag;
};

int main() {
  struct Data data;
  atomic_init(&data.value, 0);
  atomic_flag_clear(&data.flag);

  if (atomic_is_lock_free(&data)) {
    // アトミック操作を安全に実行できる
    while (!atomic_flag_test_and_set(&data.flag)) {
      // 何もしない
    }
    data.value = 100;
  } else {
    // 同期メカニズムを使用してアクセスする必要がある
    while (!atomic_flag_test_and_set(&data.flag)) {
      int value = atomic_load(&data.flag);
      if (value == 0) {
        atomic_store(&data.flag, 1);
        data.value = 100;
        break;
      }
    }
  }

  printf("data.value: %d\n", data.value);

  return 0;
}


スピンロック

スピンロックは、アトミック操作が完了するまで待機する簡単な同期メカニズムです。以下のコード例は、スピンロックを使用してカウンターをインクリメントする方法を示しています。

#include <pthread.h>

int counter = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void increment_counter() {
  pthread_mutex_lock(&lock);
  counter++;
  pthread_mutex_unlock(&lock);
}

このコードでは、pthread_mutex_lockpthread_mutex_unlock 関数を使用して、カウンターへのアクセスを同期しています。スピンロックは、短時間で頻繁にアクセスされるデータに対して有効ですが、長時間ロックされている場合はパフォーマンスが低下する可能性があります。

セマフォア

セマフォアは、複数のスレッドが共有リソースへのアクセスを制御するために使用される同期メカニズムです。以下のコード例は、セマフォアを使用してカウンターをインクリメントする方法を示しています。

#include <semaphore.h>

int counter = 0;
sem_t semaphore = {1};

void increment_counter() {
  sem_wait(&semaphore);
  counter++;
  sem_post(&semaphore);
}

このコードでは、sem_waitsem_post 関数を使用して、カウンターへのアクセスを制御しています。セマフォアは、スピンロックよりも効率的ですが、より複雑な実装が必要です。

条件変数

条件変数は、特定の条件が満たされるまでスレッドをブロックするために使用される同期メカニズムです。以下のコード例は、条件変数を使用してカウンターが特定の値に達するまで待機する方法を示しています。

#include <pthread.h>

int counter = 0;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void increment_counter() {
  pthread_mutex_lock(&lock);
  counter++;
  if (counter == 10) {
    pthread_cond_signal(&cond);
  }
  pthread_mutex_unlock(&lock);
}

void wait_for_counter() {
  pthread_mutex_lock(&lock);
  while (counter != 10) {
    pthread_cond_wait(&cond, &lock);
  }
  pthread_mutex_unlock(&lock);
}

このコードでは、pthread_cond_waitpthread_cond_signal 関数を使用して、カウンターが10になるまでスレッドをブロックしています。条件変数は、複雑な同期が必要な状況に適しています。

比較・スワップ

比較・スワップ (CAS) 命令は、アトミック操作を使用してデータを更新するために使用される命令です。以下のコード例は、CAS 命令を使用してカウンターをインクリメントする方法を示しています。

#include <atomic_ops.h>

int counter = 0;

void increment_counter() {
  int old_value;
  do {
    old_value = atomic_load(&counter);
  } while (!atomic_compare_exchange_weak(&counter, old_value, old_value + 1));
}

このコードでは、atomic_compare_exchange_weak 関数を使用して、カウンターをインクリメントしています。CAS 命令は、スピンロックやセマフォアよりも効率的ですが、すべてのコンパイラとプラットフォームでサポートされているわけではありません。