【C++】std::atomicで出来ることと典型的な使い方【標準ライブラリ】

【C++】std::atomicで出来ることと典型的な使い方【標準ライブラリ】

C++の標準ライブラリatomicで出来ることと典型的な使い方

atomicとは

C++標準ライブラリのstd::atomicは、マルチスレッド環境におけるデータ競合を防ぎながら、安全に変数を操作するための機能を提供します。 通常の変数は複数のスレッドで同時にアクセスすると競合状態(race condition)が発生する可能性がありますが、std::atomicを使うことで 競合を防ぎ、スレッドセーフなプログラムを記述できます。

基本的な使い方

std::atomicはプリミティブな型(int, bool, pointer など)に対して使用できます。基本的な使い方を示します。

#include <iostream>
#include <atomic>

int main() {
    std::atomic<int> counter(0);

    counter.store(10);  // 値をセット
    int value = counter.load();  // 値を取得
    counter++;  // アトミックにインクリメント

    std::cout << "Counter: " << counter.load() << std::endl;
    return 0;
}

メモリオーダーの概念

std::atomicにはメモリオーダー(memory order)の概念があり、これによりメモリ操作の順序を制御できます。 代表的なメモリオーダーには以下のようなものがあります。

  • memory_order_relaxed: 順序を保証しない(最も高速)
  • memory_order_acquire: 読み込みの順序を保証
  • memory_order_release: 書き込みの順序を保証
  • memory_order_seq_cst: 厳格な順序を保証(デフォルト)

以下の例では、memory_order_releasememory_order_acquireを使用して順序を制御しています。

#include <iostream>
#include <atomic>
#include <thread>

std::atomic<bool> ready(false);
int data = 0;

void producer() {
    data = 42;
    ready.store(true, std::memory_order_release);
}

void consumer() {
    while (!ready.load(std::memory_order_acquire));
    std::cout << "Data: " << data << std::endl;
}

int main() {
    std::thread t1(producer);
    std::thread t2(consumer);
    t1.join();
    t2.join();
    return 0;
}

主な操作

std::atomicでは、いくつかの基本操作が提供されています。

  • store(): 値を設定
  • load(): 値を取得
  • exchange(): 値を交換
  • fetch_add(), fetch_sub(): 加算・減算
  • compare_exchange_weak(), compare_exchange_strong(): 条件付き交換

atomic_flagの使い方

std::atomic_flagは最も低レベルのアトミック操作を提供し、通常はスピンロックの実装に使用されます。

#include <iostream>
#include <atomic>
#include <thread>

std::atomic_flag lock = ATOMIC_FLAG_INIT;

void worker(int id) {
    while (lock.test_and_set(std::memory_order_acquire));
    std::cout << "Thread " << id << " is working\n";
    lock.clear(std::memory_order_release);
}

int main() {
    std::thread t1(worker, 1);
    std::thread t2(worker, 2);
    t1.join();
    t2.join();
    return 0;
}

典型的な使用例

スレッド間カウンター

#include <iostream>
#include <atomic>
#include <thread>
#include <vector>

std::atomic<int> counter(0);

void increment() {
    for (int i = 0; i < 1000; ++i) {
        counter.fetch_add(1, std::memory_order_relaxed);
    }
}

int main() {
    std::vector<std::thread> threads;
    for (int i = 0; i < 10; ++i) {
        threads.emplace_back(increment);
    }
    for (auto& t : threads) {
        t.join();
    }
    std::cout << "Final counter value: " << counter.load() << std::endl;
    return 0;
}

atomicのパフォーマンス特性

std::atomicはスレッド間で同期を行うため、通常の変数よりもパフォーマンスが低下します。特にmemory_order_seq_cstは 負荷が高いため、適切なメモリオーダーを選択することが重要です。

まとめ

  • std::atomicを使うことで競合状態を回避できる。
  • 適切なメモリオーダーを選択することで性能を向上できる。
  • スレッドセーフなカウンターやフラグ管理に適している。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です