C++11標準ライブラリで新たに追加された Futuresライブラリ/標準ヘッダ<future>と、Boost.Threadライブラリ(Boost 1.48.0) の機能比較。
両者のFutures機能*1が提供するクラスと関数の対応関係を下表にまとめる*2。C++0xドラフト段階からBoost.Threadライブラリをベースとしているため、両ライブラリの基本的な設計方針は一致している。
2012-11-08追記:Boost.Thread 1.50.0からboost::unique_future
またはboost::future
を切り替え可能となっている(旧unique_future
は廃止予定とされており1.55.0までの提供)。また、Boost.Thread 1.52.0からboost::async
関数(launch::async
ポリシーのみ対応)が追加された。
C++11 Futures | Boost.Thread Futures |
---|---|
std::futureクラス (→id:yohhoy:20120131) |
boost::unique_futureクラス[1.55.0まで選択可] boost::futureクラス[1.50.0以降で選択可] |
std::shared_futureクラス (→id:yohhoy:20120201) |
boost::shared_futureクラス |
std::promiseクラス (→id:yohhoy:20120131) |
boost::promiseクラス |
std::packaged_taskクラス (→id:yohhoy:20120202) |
boost::packaged_taskクラス |
std::async関数 (→id:yohhoy:20120203) |
boost::async関数[1.52.0以降] |
(対応無し) | boost::wait_for_any関数 boost::wait_for_all関数 |
std::future/shared_future vs. boost::unique_future/shared_future
- 状態確認(is_ready, has_value, has_exception, get_statusメンバ関数)はBoost.Threadにのみ存在する。C++11では直接getメンバ関数で値/例外を取り出すか、タイムアウト付き待機関数で状態を確認できる。*3
タイムアウト付き待機関数のメンバ関数名が異なる。C++11ではwait_for, wait_untilに対し、Boost.Threadではtimed_wait, timed_wait_untilとなる。タイムアウト付き待機関数の戻り値型が異なる。C++11ではfutureの状態(std::future_status列挙型)を返すが、Boost.Threadではbool型を返す。*4- Boost.Threadでもboost::future_status列挙型に変更された。[1.50.0以降]
validメンバ関数はC++11にのみ存在する。- Boost.Threadにもvalidメンバ関数が追加された。[1.50.0以降]
std::promise vs. boost::promise
std::packaged_task vs. boost::packaged_task
- テンプレート引数に指定する型の要件が異なる。C++11では関数型を指定するのに対し、Boost.Threadは戻り値型を指定する。
- C++11(std::packaged_task)では任意の関数オブジェクトを指定できる。コンストラクタで引数バインドを行うか、operator()で引数を渡すかを選択可能。
- Boost.Thread(boost::packaged_task)では引数無し関数オブジェクトのみ指定できる。引数無しのoperator()のみが提供されており、引数をとる関数オブジェクトを指定する場合は予めboost::bindでバインド処理が必要。
- make_ready_at_thread_exitメンバ関数はC++11にのみ存在する。
- resetメンバ関数はC++11にのみ存在する。
- Boost.Threadにもresetメンバ関数が追加された。[1.50.0以降]
- set_wait_callbackメンバ関数はBoost.Threadにのみ存在する。C++11ではstd::async関数+std::launch::deferredポリシー動作が近い機能を実現する。(具体例を後述)
int func(int, int); // 対象の関数 int arg1, arg2; // 関数funcに渡す引数 //-------- C++11 #include <future> auto task1 = std::packaged_task<int()>(func, arg1, arg2); task1(); auto task2 = std::packaged_task<int(int, int)>(func); task2(arg1, arg2); //-------- Boost.Thread #include <boost/thread/packaged_task.hpp> auto task1 = boot::packaged_task<int>(std::bind(func, arg1, arg2)); task1();
std::async関数(C++11のみ)
Boost.Threadにもlaunch::asyncポリシー+boost::async関数が追加された(1.52.0時点ではlaunch::deferredポリシー未実装)。[1.52.0以降]
std::launch::asyncポリシーを指定したstd::async関数の動作は、boost::threadとboost::packaged_taskクラスを用いたエミュレーションが可能。*5[1.51.0まで]
int func(int, int); // 対象の関数 int arg1, arg2; // 関数funcに渡す引数 //-------- C++11 async(launch::async) #include <future> auto f = std::async(launch::async, func, arg1, arg2); // 別スレッドで関数funcが実行される int result = f.get(); //-------- Boost.Threadによるエミュレーション #include <boost/thread.hpp> boost::packaged_task<int> task(boost::bind(func, arg1, arg2)); boost::unique_future<int> f = task.get_future(); boost::thread th(boost::move(task)); int result = f.get();
std::launch::deferredポリシーを指定したstd::async関数の動作は、boost::packaged_task::set_wait_callbackメンバ関数を用いてエミュレーション可能。*6
int func(int, int); // 対象の関数 int arg1, arg2; // 関数funcに渡す引数 //-------- C++11 async(launch::deferred) #include <future> auto f = std::async(launch::deferred, func, arg1, arg2); int result = f.get(); // f.get()呼出時に関数funcが実行される //-------- Boost.Threadによるエミュレーション #include <boost/thread.hpp> boost::packaged_task<int> task(boost::bind(func, arg1, arg2)); task.set_wait_callback([](boost::packaged_task<int>& pt) { try { pt() } catch (boost::task_already_started&) {} }); boost::unique_future<int> f = task.get_future(); int result = f.get();
boost::wait_for_all関数(Boost.Threadのみ)
指定された全futureオブジェクトの非同期処理完了を待機する関数。C++11標準ライブラリでは同関数が提供されないが、単に全futureオブジェクトのwaitメンバ関数を呼び出せば同じ効果が得られる。
// wait_for_all(f1, f2)相当 f1.wait(); f2.wait();
boost::wait_for_any関数(Boost.Threadのみ)
指定されたfutureオブジェクト群のうち、少なくともどれか1つの非同期処理が完了するまで待機する関数。C++11標準ライブラリでは同関数は提供されないため、同じ機能を実現するにはstd::condition_variableを用いて完了通知機構を組み立てる必要がある。
下記コードは2つの別スレッド上の非同期処理のうち、少なくとも1つが処理完了するまで待機する実装例。
#include <thread> #include <mutex> #include <condition_variable> #include <future> struct waiter_t { std::mutex m; std::condition_variable cv; bool done[2]; waiter_t() : done{false,false} {} } waiter; void call_and_notify(std::packaged_task<int()> task, int id) { task(); std::lock_guard<std::mutex> lk(waiter.m); waiter.done[id] = true; // 自タスクの完了フラグを設定 waiter.cv.notify_all(); // 条件変数waiter.cvで待機する全スレッドに通知 } // 非同期タスクの定義とfuture取り出し auto task1 = std::packaged_task<int()>([&]()->int{ /* 処理1 */ }); auto task2 = std::packaged_task<int()>([&]()->int{ /* 処理2 */ }); auto f1 = task1.get_future(), f2 = task2.get_future(); // call_and_notify経由で非同期タスクを実行 std::thread th1(call_and_notify, std::move(task1), 0); std::thread th2(call_and_notify, std::move(task2), 1); std::unique_lock<std::mutex> lk(waiter.m); // 条件(done[0] || done[1])が偽の間は条件変数waiter.cvで待機 waiter.cv.wait(lk, [&]{ return (waiter.done[0] || waiter.done[1]); }); // done[0], done[1]の少なくとも一方はtureになっている int first_result = waiter.done[0] ? f1.get() : f2.get(); // get呼出はブロックしない
関連URL
*1:"Futures" に対応する適切な日本語訳がみあたらないため、そのまま英語表記としている。future/promiseについては wikipedia:future や wikipedia:en:futures_and_promises も参照のこと。
*2:正確にはクラステンプレートと関数テンプレートとして提供される。本記事では簡単のためテンプレートであることをいちいち明記しない。
*3:C++11標準ライブラリからはN2996で削除された。
*4:N3058でタイムアウト付き待機関数の戻り値型が変更された。
*5:http://www.boost.org/doc/html/thread/synchronization.html#thread.synchronization.futures.creating
*6:http://www.boost.org/doc/html/thread/synchronization.html#thread.synchronization.futures.lazy_futures