スレッドの切り替え
複数のスレッドを扱う際に、今すぐ他のスレッドの処理をしたい場合があります。インテルTBBにはこのための機能も用意されています。この機能もサンプルを見た方が分かりやすいと思いますので、サンプルプロジェクトYieldSampleのコードを見てください。
#include <iostream>
#include "tbb/compat/thread"
using namespace std;
/*-------------------------------------------------------------------------------
yieldメソッドの使用法を示す為だけのクラス
----------------------------------------------------------------------------------*/
class ModestFoo
{
public:
/*
他のスレッドに処理を譲ってから、自己の処理を開始します。
*/
void operator()() const
{
cout << "他のスレッドに処理を譲ります(ID:"
<< this_thread::get_id() << ")" << endl;
this_thread::yield(); //他のスレッドを実行する
cout << "ID:" << this_thread::get_id() << "の処理が始まりました" << endl;
};
};
/*-------------------------------------------------------------------------------
メインプログラム
----------------------------------------------------------------------------------*/
int main(void)
{
//2つのスレッドを開始
ModestFoo modest;
thread modestThread( modest );
ModestFoo modest1;
thread modestThread1( modest1 );
modestThread.join();
modestThread1.join();
cout << endl;
}

このサンプルは、他のスレッドを先に処理するオブジェクトを2つ用意しています。この2つのオブジェクトを並列処理すると、互いに処理を譲り合うので、結局は実行した順に実行されることになります。注目するべき所は、this_thread::yield()とthis_thread::get_id()です。yieldメソッドを使用すると、今処理している処理を中断し、他のスレッドが処理を実行できるようにできます。get_id()メソッドを使用すると、スレッドを識別するためのIDを取得できます。
yieldメソッドが必要な理由は、満足に実行できないスレッドが出現するのを避けるためです。例えば、重要な処理をしないスレッドと、重要な処理をするスレッドがあるとします。もし、yieldメソッドを使用できないならば、重要でないスレッドばかりが実行され、重要なスレッドがあまり実行されないかもしれません。この状況では、yieldメソッドが役に立ちます。また、並列処理特有のバグを再現したり、テストを行ったりする場合にも使用すれば便利です。ただし、yieldメソッドはあまり頻繁に使用するべきものではなく、正常に並列処理が行えるように設計するべきなので注意してください。もし、yieldメソッドと、sleep_forメソッドを多用しているシステムがあれば、設計が正しくされているのかチェックするべきです。なぜなら、正しく設計されたシステムは、無理やりスレッドの切り替えを行わなくても、正常に動作するように設計されているものだからです。一般的に、yieldメソッドとsleep_forメソッドが多用されているシステムは、飢餓状態になっている可能性が高いといえます。
get_id()メソッドが必要な理由は、スレッドを識別する必要があるからです。例えば、このサンプルでget_id()メソッドを使用しないと、何が何だか分からなくなります。また、デバッグ時やスレッドを管理する時に、スレッドを識別できるということは大きな武器となります。今回紹介した3つのメソッド内で一番使える状況が多いでしょう。
最後に並列処理において重要なことを解説します。このサンプルを実行すると、メッセージが乱れる場合があります。

この現象はcoutをやみくもに使用すると起こるものです。そのため、前回並列処理でcoutを多用するべきでないと言及しました。また、実務において、並列的に標準出力するのは問題がある仕様です。なぜなら、並列的にさまざまなスレッドから出力されたメッセージを顧客は必要としているのか疑問だからです。特にインテルTBBでは、スレッドを直接扱う機会は少なくなり、予期せぬ数のメッセージが出力されます。仮に将来32コア以上のCPUで動くシステムが、coutにメッセージを出力する場合、混乱を生むのは目に見えています。
このサンプルは他にも次のように出力される場合があります。

このように、予期せぬ結果を避けるため、並列処理では極力coutを避けるべきです。使用する場合は、仕様レベルから妥当性を検討する必要があります。
まとめ
今回はインテルTBBに用意されているスレッドクラスを紹介しました。そして、並列処理では極力coutを避けるべき理由も解説しました。インテルTBBは優れた技術ですが、だからと言って全ての状況に対応できません。インテルTBBに用意されている高度な並列アルゴリズムを使用できない場合、今回紹介したスレッドクラスなどを駆使することになります。
しかしながら、並列処理の初心者が、実務レベルで使いこなすのは大変難しいでしょう。この連載は基礎の解説を目的としているので、詳しいことは今回述べませんでしたが、スレッドクラスを使用して自分で独自の並列アルゴリズムや、スレッドセーフなクラスを実装するのは多くの知識が必要となります。
この連載を最後まで読んで、インテルTBBと並列処理の基礎を習得してから、改めて独自並列アルゴリズムやスレッドセーフなクラスの実装にチャレンジしてください。機会があれば、より高度な並列処理の知識を解説したいと考えています。
次回もTBBの最新情報を盛り込みつつ、並列処理についての解説を盛り込んでいきます。お楽しみに。
参考資料
書籍
- 『インテル スレッディング・ビルディング・ブロック』 James Reinderss著、菅原清文・エクセルソフト 訳、オライリー・ジャパン、2008年2月26日
