C++11標準ライブラリのstd::async
関数(→id:yohhoy:20120203)std::launch::async
ポリシーと、同関数が返すstd::future
オブジェクトの動作についてメモ。本記事はStack Overflow上での質問と回答内容に基づく。
要約:async
関数動作でlaunch::async
ポリシーが選択された場合のみ、future
オブジェクトのデストラクタではasync
関数が作成した新スレッド完了を待機する。(暗黙的にスレッドjoinが行われる。)
2014-03-15追記:id:yohhoy:20121004, id:yohhoy:20140315 も参照のこと。
何が起こるのか?
下記コードでは、async
関数呼び出しによってfoo()
は新しいスレッド上で実行される。一方メインスレッドは、async
関数が返したfuture
オブジェクトの「デストラクタ呼び出しで別スレッド完了するまでブロック」される。
#include <thread> #include <future> void foo() { // (1) 5秒待機... std::this_thread::sleep_for(std::chrono::seconds(5)); } int main() { { auto f = std::async( std::launch::async, foo ); // (2) fのデストラクタでfooの完了を待機する } }
状況をさらに単純化したものが下記コード:
std::async(std::launch::async, foo);
async
関数が返すfuture
オブジェクトの寿命は文末セミコロンまでとなり、関数foo
の処理が完了するまでメインスレッドはブロックされる。つまりfoo()
が別スレッド上で実行されることを除いて、通常の関数呼び出しと同様に逐次実行される。
類似処理との差異
前述の動作は「async
関数が返すfuture
オブジェクト」かつ「処理系によってlaunch::async
ポリシーが選択されたとき」のみ生じる。それ以外のケースにおいては、future
オブジェクトのデストラクタでは何もしない事に注意。
// launch::deferredポリシー動作 { auto f = std::async( std::launch::deferred, foo ); // fのデストラクタではブロックしない // (そもそも関数fooは呼び出されない) }
// 既定の起動ポリシー(launch::async、deferred両指定と等価) { auto f = std::async( foo ); // 処理系依存: launch::asyncポリシーが選択された場合のみブロック }
// packaged_task版(launch::asyncポリシー動作を近似) { std::packaged_task<void()> task(&foo); auto f = task.get_future(); std::thread(std::move(task)).detach(); // fのデストラクタではブロックしない }
C++仕様での言及箇所
N3337 30.6.8/p5より部分引用(下線部は強調)。厳密には「shared stateを最後にリリースする関数(=future/shared_futureデストラクタ)呼び出し」において、関連するスレッドの完了を待機する。
Synchronization: (snip)
If the implementation chooses the launch::async policy,
- (snip)
- the associated thread completion synchronizes with (1.10) the return from the first function that successfully detects the ready status of the shared state or with the return from the last function that releases the shared state, whichever happens first.