C++におけるマルチスレッド処理とstd::async、std::future、std::atomic、volatileの使用法

C++標準ライブラリはスレッド操作を包括的にサポートしており、特にstd::threadやstd::asyncが頻繁に利用されます。『Effective Modern C++』では、スレッドのネイティブなローカル処理が必要でない限り、タスクベースのプログラミングを推奨しており、std::asyncの使用が望ましいとされています。本記事では、std::asyncを中心にその仕組みと使用法を解説します。

std::asyncはタスクベースの抽象化により、std::threadよりも柔軟性を持ち、非同期結果の取得が可能です。クロージャをstd::asyncに渡すと、std::futureオブジェクトが返され、wait()メソッドでスレッド終了を待機し、get()で結果を取得できます。

std::futureは以下の3つの状態を持ちます:

  • std::future_status::ready:タスク完了
  • std::future_status::timeout:タイムアウト
  • std::future_status::deferred:実行未開始

状態確認にはwait_for()メソッドを使用します。

auto result = std::async(performTask);
... // 他の処理
result.wait(); // バリア設定
auto value = result.get(); // 結果取得

システムがサポートするスレッド数には限界があり、それを超えるとstd::system_error例外が発生します。また、ハードウェアスレッド数を超えるソフトウェアスレッドの生成(オーバーアロケーション)も問題となることがあります。

std::asyncには2つの起動戦略があります:

  • std::launch::async:別スレッドでの実行
  • std::launch::deferred:get()またはwait()呼び出し時に実行

デフォルトでは両方の戦略をORで許容しており、これはスレッドの作成・破棄をライブラリに任せることでリソース管理を簡略化します。この柔軟性がstd::asyncの利点です。

注意点として、デフォルト戦略ではスレッドの実行状態(同期/非同期)を把握できないため、予測困難な挙動が生じる可能性があります。

std::futureの破棄時、std::threadと異なり例外を発生しません。これは、futureが通信チャネルの一端であり、計算結果を送信する側(被呼ぶ側)がstd::promise経由で共有状態(shared state)に結果を書き込み、呼び出し側がfutureで読み取る仕組みだからです。

共有状態は通常ヒープ上に配置されますが、標準では具体的な実装は規定されていません。以下は関係者のイメージ図(破線=情報流れ):

std::futureが以下の条件を満たす場合、デストラクタで暗黙のjoinが実行されます:

  • 共有状態がstd::async呼び出しで生成された場合
  • 起動戦略がstd::launch::asyncの場合
  • その共有状態を参照する最後のfutureである場合

std::atomicは原子的な操作を可能にし、読み書きやインクリメント/デクリメントが1つの不可分な操作として行われます。これによりデータ競合による未定義動作を防げます。

std::atomicは移動構築や移動代入をサポートせず、load()・store()メソッドで明示的に操作します。一方、volatile修飾変数は原子性を保証しません。

以下はメモリオーダーの例:

int importantValue = calculateImportantValue(); // 値計算
readyFlag = true; // 別スレッドに通知

このコードでは、コンパイラが命令順序を変更する可能性があります。これを防ぐにはstd::atomicを使用します:

std::atomic<bool> readyFlag(false);
int importantValue = calculateImportantValue(); // 値計算
readyFlag = true; // 別スレッドに通知

volatileはメモリ読み込みの最適化を無効化するだけで、メモリオーダーの制御はできません。

要約すると:

  • std::atomic:ロックを使わずにスレッド安全な変数操作を実現
  • volatile:特別なメモリ領域の最適化を防ぐためのキーワード

本記事は『Effective Modern C++』を参考にしつつ、筆者の考察を加えたものです。

タグ: C++ std::async std::future std::atomic volatile

5月30日 14:48 投稿