yohhoyの日記

技術的メモをしていきたい日記

async関数launch::asyncポリシーとfutureのちょっと特殊な動作

C++11標準ライブラリのstd::async関数(→id:yohhoy:20120203std::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.