std::async / std::future
『C++』では『<future>』ヘッダに含まれる『std::async』と『std::future』を使うと、スレッドの生成・join を直接書かずに非同期処理を簡潔に記述できます。『std::async』は関数を非同期に起動し、『std::future』を通じて後から結果を取り出す仕組みです。スレッドの生存管理が自動化されるため、『std::thread』より安全に非同期処理を扱えます。
構文
// ========================================
// std::async / std::future の基本構文
// ========================================
#include <future>
#include <iostream>
// ----------------------------------------
// std::async で関数を非同期起動します
// ----------------------------------------
int compute() { return 42; }
// std::launch::async → 必ず新スレッドで即座に起動します
// std::launch::deferred → get() が呼ばれたときに同一スレッドで遅延実行します
// 省略時はどちらになるか実装依存です(通常は async を明示します)
std::future<int> fut = std::async(std::launch::async, compute);
// ----------------------------------------
// std::future::get() で結果を受け取ります
// ----------------------------------------
int result = fut.get(); // 完了まで待機してから結果を取得します
// get() は一度しか呼べません(2回目は未定義動作です)
// ----------------------------------------
// std::future::wait() / wait_for() で待機します
// ----------------------------------------
fut.wait(); // 結果が揃うまでブロックします(get() と異なり値を取りません)
// 指定時間だけ待機します
std::future_status status = fut.wait_for(std::chrono::seconds(1));
if (status == std::future_status::ready) {
// 結果が用意されています
} else if (status == std::future_status::timeout) {
// タイムアウトしました
} else {
// deferred 状態で未実行です
}
// ----------------------------------------
// std::promise:手動で値を設定します
// ----------------------------------------
std::promise<int> prom;
std::future<int> fut2 = prom.get_future();
// 別スレッドや任意のタイミングで値をセットします
prom.set_value(100); // fut2.get() が返す値を設定します
// prom.set_exception(...) // 例外を伝播することもできます
構文一覧
| 構文・クラス | 概要 |
|---|---|
| std::async(policy, f, args...) | 関数『f』を非同期に起動し『std::future』を返します。『policy』に『std::launch::async』を指定すると新スレッドで即時実行します。 |
| std::future<T> | 非同期処理の将来の結果を表すオブジェクトです。『get()』を呼ぶと結果が揃うまで待機します。 |
| future.get() | 非同期処理の結果を取得します。完了していなければブロックします。一度しか呼べません。 |
| future.wait() | 結果が揃うまで待機します。値は取り出しません。 |
| future.wait_for(duration) | 指定時間待機し『std::future_status』を返します。タイムアウト検出に使います。 |
| future.valid() | future が有効な共有状態を持っているかを返します。『get()』後は『false』になります。 |
| std::promise<T> | future に値または例外を手動でセットするオブジェクトです。『get_future()』で対応する future を取得します。 |
| promise.set_value(v) | 対応する future が返す値をセットします。 |
| promise.set_exception(e) | 対応する future に例外を伝播します。future の『get()』時に再スローされます。 |
| std::shared_future<T> | 複数回『get()』できる共有可能な future です。『future::share()』で変換します。 |
サンプルコード
eva_async.cpp
// ========================================
// eva_async.cpp
// NERV がエヴァパイロットに各種データ解析を
// 非同期で依頼し、葛城ミサトが結果をまとめる
// std::async / std::future / std::promise を
// 使ったサンプルです
// ========================================
#include <iostream>
#include <future>
#include <chrono>
#include <string>
#include <stdexcept>
// ========================================
// 碇シンジ: 使徒の A.T.フィールド強度を解析します
// 重い解析処理をスリープで表現しています
// ========================================
int analyzeShinji(const std::string& angelName) {
std::cout << "[碇シンジ] " << angelName
<< " の A.T.フィールド解析を開始します..." << std::endl;
// 解析に時間がかかる処理を模擬します
std::this_thread::sleep_for(std::chrono::milliseconds(300));
// 解析結果として A.T.フィールド強度値を返します
std::cout << "[碇シンジ] 解析完了。逃げちゃダメだ、逃げちゃダメだ..." << std::endl;
return 8750; // A.T.フィールド強度値(単位: クロスセクション)
}
// ========================================
// 綾波レイ: 使徒のコア位置を特定します
// ========================================
std::string analyzeRei(const std::string& angelName) {
std::cout << "[綾波レイ] " << angelName
<< " のコア位置特定を開始します。" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(200));
std::cout << "[綾波レイ] 特定完了。不思議と思わない。私はそういうものだから。" << std::endl;
return "胸部中央、深度 12.5m";
}
// ========================================
// 惣流・アスカ・ラングレー: 弐号機の出力値を計算します
// 失敗するケースを例外で表現しています
// ========================================
double calcAsuka(bool syncRateOk) {
std::cout << "[アスカ] 弐号機の最大出力計算を開始するわよ!" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(150));
if (!syncRateOk) {
// シンクロ率が低い場合は例外をスローします
throw std::runtime_error("シンクロ率が低すぎる!アタシらしくない!");
}
std::cout << "[アスカ] 計算完了。当然ね、天才だもの。" << std::endl;
return 14.5; // 最大出力(単位: 万 kW)
}
// ========================================
// 渚カヲル: std::promise を使って
// 使徒同定コードを葛城ミサトに直接渡します
// promise はコールバックや外部イベントで値を
// 設定したいときに便利です
// ========================================
void kaworu(std::promise<std::string>& prom) {
std::cout << "[渚カヲル] 使徒同定コードの生成を開始します。"
<< std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(250));
// promise に値をセットすると、対応する future の get() が返ります
prom.set_value("TABRIS-17-OMEGA");
std::cout << "[渚カヲル] 生成完了。君に会えてよかった。" << std::endl;
}
// ========================================
// メイン(葛城ミサト): 各パイロットの解析結果を
// 非同期に収集してまとめます
// ========================================
int main() {
std::cout << "=== NERV 使徒迎撃データ統合システム ===" << std::endl;
std::cout << "[葛城ミサト] 各パイロットに解析を依頼します。" << std::endl;
std::cout << std::endl;
const std::string targetAngel = "第17使徒タブリス";
// ----------------------------------------
// std::async で各パイロットの処理を並行起動します
// std::launch::async を指定すると即座に新スレッドが起動します
// ----------------------------------------
std::future<int> futShinji =
std::async(std::launch::async, analyzeShinji, targetAngel);
std::future<std::string> futRei =
std::async(std::launch::async, analyzeRei, targetAngel);
// アスカはシンクロ率 OK 前提で呼び出します(第2引数が true)
std::future<double> futAsuka =
std::async(std::launch::async, calcAsuka, true);
// ----------------------------------------
// カヲルは std::promise を使って値を設定します
// ----------------------------------------
std::promise<std::string> kaworuPromise;
std::future<std::string> futKaworu = kaworuPromise.get_future();
// promise を参照渡しするスレッドを std::async で起動します
std::future<void> kaworuThread =
std::async(std::launch::async, kaworu, std::ref(kaworuPromise));
std::cout << std::endl;
std::cout << "[葛城ミサト] 各解析の完了を待機中..." << std::endl;
std::cout << std::endl;
// ----------------------------------------
// wait_for でタイムアウト付き待機を行います
// ----------------------------------------
std::future_status shinji_status =
futShinji.wait_for(std::chrono::seconds(2));
if (shinji_status == std::future_status::ready) {
// 結果が揃っている場合は get() で取得します
int atFieldStrength = futShinji.get();
std::cout << "[葛城ミサト] シンジ解析結果: A.T.フィールド強度 = "
<< atFieldStrength << " クロスセクション" << std::endl;
} else {
std::cout << "[葛城ミサト] シンジの解析がタイムアウトしました。" << std::endl;
}
// ----------------------------------------
// 綾波の結果を get() で取得します
// まだ完了していなければここでブロックします
// ----------------------------------------
std::string corePosition = futRei.get();
std::cout << "[葛城ミサト] レイ解析結果: コア位置 = " << corePosition << std::endl;
// ----------------------------------------
// アスカの結果を例外ハンドリングつきで取得します
// std::async 内でスローされた例外は get() で再スローされます
// ----------------------------------------
try {
double maxOutput = futAsuka.get();
std::cout << "[葛城ミサト] アスカ計算結果: 弐号機最大出力 = "
<< maxOutput << " 万kW" << std::endl;
} catch (const std::exception& e) {
std::cout << "[葛城ミサト] アスカのタスクで例外発生: " << e.what() << std::endl;
}
// ----------------------------------------
// カヲルの promise 経由の結果を取得します
// ----------------------------------------
std::string angelId = futKaworu.get();
kaworuThread.get(); // カヲルスレッドの終了も待ちます
std::cout << "[葛城ミサト] カヲル同定コード: " << angelId << std::endl;
// ----------------------------------------
// 全データ統合完了
// ----------------------------------------
std::cout << std::endl;
std::cout << "=== 全解析完了。迎撃作戦を開始します。行きなさい! ===" << std::endl;
return 0;
}
# コンパイルします(-lpthread でスレッドライブラリをリンクします) g++ -std=c++11 eva_async.cpp -o eva_async -lpthread && ./eva_async === NERV 使徒迎撃データ統合システム === [葛城ミサト] 各パイロットに解析を依頼します。 [碇シンジ] 第17使徒タブリス の A.T.フィールド解析を開始します... [綾波レイ] 第17使徒タブリス のコア位置特定を開始します。 [惣流・アスカ・ラングレー] 弐号機の最大出力計算を開始するわよ! [渚カヲル] 使徒同定コードの生成を開始します。 [惣流・アスカ・ラングレー] 計算完了。当然ね、天才だもの。 [綾波レイ] 特定完了。不思議と思わない。私はそういうものだから。 [渚カヲル] 生成完了。君に会えてよかった。 [葛城ミサト] 各解析の完了を待機中... [碇シンジ] 解析完了。逃げちゃダメだ、逃げちゃダメだ... [葛城ミサト] シンジ解析結果: A.T.フィールド強度 = 8750 クロスセクション [葛城ミサト] レイ解析結果: コア位置 = 胸部中央、深度 12.5m [葛城ミサト] アスカ計算結果: 弐号機最大出力 = 14.5 万kW [葛城ミサト] カヲル同定コード: TABRIS-17-OMEGA === 全解析完了。迎撃作戦を開始します。行きなさい! ===
よくあるミス: std::future の get() を複数回呼ぶ
『std::future::get()』は一度しか呼べません。2回目以降の呼び出しは未定義動作になります。複数箇所から結果を参照したい場合は『std::shared_future』に変換します。
// NG: get() を2回呼ぶ(未定義動作)
std::future<int> fut = std::async(std::launch::async, []() { return 42; });
int result1 = fut.get(); // 1回目: OK
int result2 = fut.get(); // 2回目: 未定義動作(std::future_error が発生することが多い)
OK: 『share()』で『std::shared_future』に変換してから複数回『get()』を呼びます。
// OK: shared_future に変換する
std::shared_future<int> sfut = std::async(std::launch::async, []() { return 42; }).share();
int result1 = sfut.get(); // OK
int result2 = sfut.get(); // OK(shared_future は複数回呼べます)
g++ -std=c++11 ok_example.cpp -o ok_example -lpthread ./ok_example result1: 42 result2: 42
よくあるミス: launch::async を指定しない
起動ポリシーを省略すると実装依存(『async』または『deferred』)になります。即時スレッドが必要な場合は『std::launch::async』を明示します。
// 注意: ポリシー省略は実装依存 auto fut = std::async(computeHeavyTask); // deferred になる可能性がある // OK: async を明示して即座に新スレッドで起動する auto fut = std::async(std::launch::async, computeHeavyTask);
概要
『std::async』は『std::thread』よりも高レベルな非同期実行の仕組みです。『std::launch::async』を指定すると新しいスレッドが即座に起動し、戻り値として『std::future』が返ります。『std::future::get()』を呼ぶと処理完了まで待機してから結果を取得でき、スレッドの join 管理は自動で行われます。非同期処理内でスローされた例外は『future』内部に保持され、『get()』の呼び出し時に再スローされるため、『try』-『catch』で安全にハンドリングできます。『std::promise』はコールバックや外部イベントなど、関数の戻り値では表現しにくいケースで値を『future』に渡すために使います。『promise::set_value()』を呼んだ時点で、対応する『future::get()』がブロックを解除して値を返します。『std::future』は一度『get()』を呼ぶと無効になりますが、『future::share()』で『std::shared_future』に変換すると複数のスレッドから同じ結果を参照できます。コンパイルには『-lpthread』(Linux 環境)のリンクが必要です。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。