導入
マルチスレッドプログラミングにおいて、共有データへの安全なアクセスは最も頭を悩ませる課題の一つです。特にスマートポインタであるstd::shared_ptrは、ポインタのコピーや破棄を管理する「制御ブロック」を内部で持っていますが、ポインタ変数自体の書き換えはスレッドセーフではありません。C++20以前は、std::shared_ptrをスレッド間で安全に共有するためにstd::mutexを用いる必要があり、これがパフォーマンスのボトルネックになることもありました。C++20で導入されたstd::atomic
基礎知識
まず前提として、std::shared_ptrは「参照カウント」を管理することで、オブジェクトの生存期間を自動制御します。しかし、std::shared_ptrのオブジェクトそのもの(ポインタが指す先を入れ替える操作など)は、デフォルトではアトミックではありません。複数のスレッドから同時に同じshared_ptr変数にアクセスして代入操作を行うと、最悪の場合データ競合が発生し、プログラムがクラッシュします。std::atomicは、ある変数への操作が「一瞬で完了するように見える」ことを保証するテンプレートクラスであり、これにstd::shared_ptrを組み合わせることで、ポインタの付け替えを安全に行えるようになります。
実装/解決策
std::atomic
サンプルプログラム
以下のコードは、複数のスレッドから安全にshared_ptrを更新する例です。
include
include
include
include
int main() {
// スレッドセーフなshared_ptrのアトミック管理
std::atomic
// 初期化
atomic_ptr.store(std::make_shared
auto worker = [&atomic_ptr](int id) {
// 現在のポインタを取得して新しい値で更新
std::shared_ptr
auto new_ptr = std::make_shared
// compare_exchange_strongを使ってスレッドセーフに更新を試みる この機能は非常に便利ですが、注意すべき点がいくつかあります。まず、std::atomic
// 成功するまでループして更新を確定させる
while (!atomic_ptr.compare_exchange_weak(old_ptr, new_ptr)) {
// 失敗した場合はold_ptrが最新の状態に更新されるため再試行
}
std::cout << "スレッド " << id << " が値を更新しました" << std::endl;
};
std::vector
for (int i = 1; i <= 5; ++i) {
threads.emplace_back(worker, i);
}
for (auto& t : threads) {
t.join();
}
std::cout << "最終値: " << atomic_ptr.load() << std::endl;
return 0;
}
応用・注意点

コメント