C++スレッド遅延開始の実装5パターン
局所的に「スレッド開始を遅延させる」ネタが盛り上がっていたので、C++とBoostライブラリを用いた色々な実装方法をまとめてみました。
この記事で対象とするのは、下記コードにある2つの要件を満たす実装方法です。
- (1) スレッドを管理するオブジェクトXのコンストラクト時ではなく、その後の任意タイミングで新スレッド処理を開始する。
- (2) オブジェクトXのデストラクト時に、上記(1)の別スレッドがまだ実行中ならそのスレッド処理完了を待機する。
class X { // threadオブジェクトを保持するメンバ変数 void do_() { /* 別スレッド処理 */ } public: ~X() { // (2) 別スレッドがまだ実行中なら完了を待機する } void start() { // (1) 新しいスレッドを開始して関数do_を実行する } }; int main() { X x; x.start(); }
実装方法の比較
#1,2,3は@cpp_akiraさんの std::threadをあとから開始。それとムーブ対応したコンテナについて - Faith and Brave - C++で遊ぼう、#4は@egtraさんのイグトランスの頭の中(のかけら) » Boost.ThreadとBoost.Optionalからのパク(ry引用です。#5が新たに追加した実装方法です。
実装方法 | C++03/Boost | C++11/Boost | C++11/std | |
---|---|---|---|---|
#1 | SmartPtr+thread | ○ | ○ | ○ |
#2 | thread.swap | ○ | ○ | ○ |
#3 | threadムーブ | ○ | ○ | ○ |
#4 | Boost.Optimal+thread | ○ | ○ | × |
#5 | future+async() | × | × | ○ |
表中の"C++03/Boost"はC++03準拠コンパイラ+Boostライブラリ、"C++11/Boost"はC++11準拠コンパイラ+Boostライブラリ、"C++11/std"はC++11準拠コンパイラ+標準ライブラリのみの範囲において、各実装方法が実現可能/不可を示します。(なお、Boost 1.49.0を前提としています。)
注意:後述コードサンプルではC++11ラムダ式を使用しており、C++11非準拠コンパイラではコンパイルできません。C++03の範囲内では下記の書き換えが必要です。
boost::thread([this] { do_(); }) // ↓ boost::thread(&X::do_, this)
#1: SmartPtr+thread
#include <boost/shared_ptr.hpp> #include <boost/thread.hpp> class X { boost::shared_ptr<boost::thread> thread_; void do_() { /*...*/ } public: ~X() { if (thread_) thread_->join(); } void start() { thread_.reset(new boost::thread([this] { do_(); })); } };
(個人的には、コピー可能なスマートポインタshared_ptrより、ムーブのみを許容するunique_ptrの方が好ましい気がします。)
#2: thread.swap
#include <boost/thread.hpp> class X { boost::thread thread_; void do_() { /*...*/ } public: ~X() { if (thread_.joinable()) thread_.join(); } void start() { boost::thread thd([this] { do_(); }); thread_.swap(thd); } };
#3: threadムーブ
#include <thread> class X { std::thread thread_; void do_() { /*...*/ } public: ~X() { if (thread_.joinable()) thread_.join(); } void start() { thread_ = std::thread([this] { do_(); }); } };
#4: Boost.Optimal+thread
#include <boost/optional.hpp> #include <boost/utility/in_place_factory.hpp> #include <boost/thread.hpp> class X { boost::optional<boost::thread> thread_; void do_() { /*...*/ } public: ~X() { if (thread_) thread_->join(); } void start() { thread_ = boost::in_place([this] { do_(); }); } };
#5: future+async()
この実装方法は前述のものとは異なり、threadオブジェクトの明示的な生成/保持を行いません。代わりに、launch::asyncポリシーasync関数によるスレッド開始と、futureオブジェクトのデストラクタによる(暗黙的)スレッド完了待機を行います。暗黙的なスレッドjoinについてはasync関数launch::asyncポリシーとfutureのちょっと特殊な動作 - yohhoyの日記を参照下さい。
#include <future> class X { std::future<void> task_; void do_() { /*...*/ } public: // task_がlaunch::async起動されたfutureオブジェクトの場合に限り、 // future<void>::~future()にてスレッドjoinが暗黙的に行われる。 void start() { task_ = std::async(std::launch::async, [this] { do_(); }); } };
実装#1~#4では関数do_()から例外送出されるとプログラム終了(std::terminate)しますが、実装#5は関数do_()で送出した例外を無かったこととして処理続行します。正確に表現すると、例外はちゃんとfutureオブジェクト中に格納されています。上記コードではfuture
各実装はgcc 4.6.3@Ubuntu 11.10にて動作確認済みです。(なお#5はgcc 4.7.0@MacOS X 10.6だとdo_からの例外送出時のみSegmentation Faultが発生。)