C++11標準ライブラリで新しく追加されたstd::packaged_task
についてメモ。
関連記事:id:yohhoy:20120131, id:yohhoy:20120201
packaged_taskの基本
packaged_taskは関数オブジェクト*1を “非同期タスク” として保持するクラステンプレート。packaged_taskが提供する機能は、「std::function
による関数オブジェクト保持+future/promiseによる同期機構と処理結果の受け渡し」のイメージが近い。
- packaged_taskテンプレート引数には関数型のみ許容される。例:
int
型引数を1つとりdouble
型を返す非同期タスクなら、packaged_task<double(int)>
となる。 - packaged_taskが参照するshared stateは、関数オブジェクトとその処理結果のみ保持する。packaged_taskでは関数オブジェクトに渡す実引数は保持しないことに注意。関数オブジェクトに対する実引数は、
packaged_task::operator()
呼び出しのときに渡す。(実引数の扱いはstd::function
と同じ考え方。) - 処理結果の設定は、関数オブジェクトから普通に値を返す(return)か例外送出(throw)すればよい。(
std::promise
のset_value
/set_exception
相当をライブラリ側が自動的に行う。) - 処理結果の取り出しはpackaged_taskオブジェクトから取り出した
std::future
を介して行う。futureオブジェクトは、packaged_task::get_future
メンバ関数呼び出しにて作成する。(処理結果の取り出しについてはfuture/promiseの関係と同じ。)
packaged_task単体ではスレッド生成は行なわないため、別スレッド上で非同期タスクを実行するためにstd::thread
と組み合わせて利用する*2。
#include <thread> #include <future> int main() { // int型の引数を1つとってdouble型を返す非同期タスク std::packaged_task<double(int)> task([](int x) -> double { if (/*...*/) { double value = /* 何らかの計算 */ return value; // (2a) 処理結果を返す } else { throw std::exception(); // (2b) 例外throw } }); std::future<double> f = task.get_future(); std::thread th(std::move(task), 42); // (1) 別スレッドで非同期タスクtaskを実行 th.detach(); /* 自スレッドでの処理 */; try { double result = f.get(); // (3a) 処理結果を取り出し(別スレッドでの処理完了を待機) } catch (...) { // (3b) 非同期タスク内でthrowされた例外が再throwされる } return 0; }
実引数のバインド処理
double process(int x) { /*...*/ } //-------- (A) packaged_task::operator()で実引数を渡す const int arg = 42; std::packaged_task<double(int)> task( process ); std::thread th( std::move(task), arg ); //-------- (B) 予め実引数をbindした関数オブジェクトを渡す const int arg = 42; std::packaged_task<double()> task( std::bind(process, arg) ); std::thread th( std::move(task) );
その他
get_future
メンバ関数で2回以上futureを取り出そうとすると、例外future_error/エラーコードfuture_already_retrievedが送出される。複数スレッドでfutureを共有するケースではstd::shared_future
を利用する。operator()
メンバ関数呼び出しによってshared stateが保持する関数オブジェクトを実行し、shared stateに処理結果が設定されてready状態となる。関数オブジェクトが例外を送出した場合は処理結果として扱われるため、operator()
メンバ関数呼び出し自体は基本的に成功する。処理結果として格納された例外オブジェクトはfuture::get()
呼び出しのときに再throwされる。operator()
メンバ関数呼び出しを2度以上行うと、同関数から例外future_error/エラーコードpromise_already_satisfiedが送出される。- 関数オブジェクトの実行と結果設定は行うが、取り出し可能とするタイミングをスレッド終了後まで遅延させるために
make_ready_at_thread_exit
メンバ関数が用意されている。同スレッド上のスレッドローカル変数(記憶クラス指定子にthread_localをつけた変数)が破棄された後に、shared stateがready状態に遷移する。
関連URL