【C++学習|実務向け】C++並列処理の要:std::futureとstd::promiseによる非同期タスク管理の実践

1. 導入

現代のC++開発において、UIのフリーズ防止や計算時間の短縮のために「非同期処理」は必須の技術です。しかし、別スレッドで計算した結果をメインスレッドに安全に受け渡すのは意外と骨が折れる作業です。std::futureとstd::promiseを活用することで、スレッド間のデータの受け渡しを簡潔かつ型安全に行うことが可能になります。本記事では、この仕組みの基礎から実務上の注意点までを解説します。

2. 基礎知識

std::promiseは「結果を書き込む側」、std::futureは「結果を読み取る側」という役割分担をしています。これらは内部で「共有状態」を保持しており、promiseで値をセットすると、それに対応するfutureがその値を受け取れるという仕組みです。

仕組みのポイント:
・promise: set_value() で結果を格納する。
・future: get() で結果を取得する。結果がまだない場合は、計算が終わるまで待機(ブロック)する。

3. 実装/解決策

実務では、単に別スレッドを立ち上げるだけでなく、例外が発生した場合のハンドリングも重要になります。std::promiseは set_exception() メソッドを提供しており、非同期タスク内で発生したエラーをメインスレッド側へ通知して再スローさせることが可能です。これにより、メインスレッド側でスマートにエラー処理を統合できます。

4. サンプルプログラム

以下は、バックグラウンドでの重い計算結果をfutureで受け取る典型的な実装例です。

include
include
include include

int main() {
// 1. promiseとfutureのペアを作成
std::promise promise;
std::future future = promise.get_future();

// 2. 別スレッドで計算を実行
std::thread worker([p = std::move(promise)]() mutable {
try {
// 重い処理を想定
std::this_thread::sleep_for(std::chrono::seconds(2));
int result = 42;

// 計算結果をセット
p.set_value(result);
} catch (…) {
// 例外が発生した場合はpromise経由でエラーを渡す
p.set_exception(std::current_exception());
}
});

// メインスレッド側の処理
std::cout << "計算中..." << std::endl; // 3. 結果を待機 (get()は値がセットされるまでブロックします) try { int value = future.get(); std::cout << "計算結果: " << value << std::endl; } catch (const std::exception& e) { std::cerr << "エラーが発生しました: " << e.what() << std::endl; } worker.join(); return 0; }

5. 応用・注意点

実務で利用する際は、以下の2点に特に注意してください。

パフォーマンスのボトルネック:
future.get() は待機時にブロックするため、メインスレッド(特にGUIのメインループなど)で多用するとアプリの応答性が著しく低下します。待機が必要な場合は、future::wait_for() を用いてタイムアウトを設定する、あるいはstd::asyncでタスクを管理するなどの工夫が必要です。

コスト:
std::future/promiseは内部で動的メモリ確保(ヒープ)と同期用のミューテックスを使用します。超低遅延が求められるゲームエンジンや金融取引システムなどのホットパス(頻繁に呼ばれるコード)では、これらを使うよりも、アトミック変数やリングバッファを用いたロックフリーなキューを自作する方がパフォーマンス的に有利な場合があります。まずはstd::futureで堅牢に実装し、ボトルネックが判明した段階で最適化を検討するアプローチをお勧めします。

コメント

タイトルとURLをコピーしました