はじめに
C++の新しい標準規格である「C++0x」の大きな新機能の1つが、マルチスレッド処理のサポートです。
従来のC++では、マルチスレッド機能は標準規格の拡張としてコンパイラごとに提供されていたため、細かな部分がコンパイラやプラットフォームによって異なっていました。しかしC++0xでは、すべてのコンパイラが同じメモリモデルに準拠し、同一のマルチスレッド機能を利用できることになります(ただし、従来同様の拡張をコンパイラが独自に提供することも可能です)。
開発者の立場からすると、マルチスレッドのコードを別のコンパイラやプラットフォームへ移植するときの手間を大きく省くことができます。複数のプラットフォーム向けの開発を行う場合でも、種々雑多なAPIや構文をいくつも頭に入れておく必要がありません。
新しいスレッドライブラリの中心を担うのは、実行スレッドを制御するstd::thread
クラスです。まずはこれから見ていくことにしましょう。
スレッドの起動
新しいスレッドを起動するには、std::thread
のインスタンスを生成し、関数を引数に渡します。この関数は新しいスレッドのエントリポイントとなります。この関数から処理が戻ると、スレッドも終了します。
void do_work(); std::thread t(do_work);
これだけ見ると、従来使用してきたスレッド生成用のAPIと大差ありません。しかし、大きな違いが1つあります。C++なので、渡せるのは関数だけとは限らないという点です。標準C++ライブラリの各種アルゴリズムと同じで、関数呼び出し演算子(operator()
)を実装したクラスのオブジェクトを、通常の関数と同様にstd::thread
に渡すことができるのです。
class do_work { public: void operator()(); }; do_work dw; std::thread t(dw);
この処理では、引数に渡したオブジェクトがスレッド内に実際にコピーされるという点が要注意です。指定したオブジェクトそのものを参照渡しで使いたい場合は、std::ref
でラップします(ただしその場合、スレッドの完了前にそのオブジェクトを破棄することがないよう注意が必要です)。
do_work dw; std::thread t(std::ref(dw));
大抵のスレッド作成APIでは、作成するスレッドに対して1つのパラメータ(通常はlong値やvoid*値)を渡します。std::thread
にも引数を渡せますが、数の制限はなく、ほぼすべての型を使用できます。「数の制限がない」という点が大きな特徴です。C++0xで新たに導入された可変個引数テンプレート(Variadic Templates)機能をコンストラクタで使うことで、可変個の引数に対応できます(リンク先はPDF)。構文は従来のvararg(...)と似ていますが、タイプセーフな処理が可能です。
では、コピー可能な任意の型のオブジェクトを、スレッド関数に引数として渡してみましょう。次のようになります。
void do_more_work(int i,std::string s,std::vector<double> v); std::thread t(do_more_work,42,"hello",std::vector<double>(23,3.141));
関数オブジェクト単独の場合と同様に、各引数は関数の呼び出し前にスレッドにコピーされます。参照渡しにしたい場合はstd::ref
でラップします。
void foo(std::string&); std::string s; std::thread t(foo,std::ref(s));
スレッドの起動についてはこれくらいにして、次はスレッド終了の待機です。C++標準ではこれを、POSIXの用語にならって、スレッドの「ジョイン(join)」と呼んでいます。使用するメンバ関数はjoin()
です。
void do_work(); std::thread t(do_work); t.join();
スレッドのジョインを行わない場合は、スレッドオブジェクトを単に破棄するか、detach()
を呼び出します。
void do_work(); std::thread t(do_work); t.detach();
こうしてスレッドを起動および終了できますが、スレッド間でデータを共有するとしたら、保護のための処理も必要になります。新しいC++標準ライブラリにはそのための機能もあります。