【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_release
とmemory_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
を使うことで競合状態を回避できる。- 適切なメモリオーダーを選択することで性能を向上できる。
- スレッドセーフなカウンターやフラグ管理に適している。