はじめに
この連載では、並列処理を高度に抽象化したインテルTBBを通じて、並列化の考え方を取得することを目的としています。今後、並列化は当たり前のものとなり、さまざまな形でサポートされるようになります。並列化処理の根底に流れる考え方を身につければ、その変化に対応できます。
今回はスレッドセーフおよびインテルTBBに用意されている4個のコンテナの使い方と注意するべき点について解説します。この連載のサンプルはあくまでもインテルTBBの使い方を説明するものであり、実務を特別に意識したものではありません。その点をご理解ください。
対象読者
筆者が想定している読者はC++の基本的文法を理解し、並列化プログラミングに興味を持っている方です。高度なC++テクニックを極力さけ、基本的な文法さえ分かれば読めるように極力注意しますので、並列化に興味を持っている方はぜひこの連載に目を通してください。
必要な環境
C++コンパイラが必要です。お持ちでない方は無償で提供されているマイクロソフト社の「Visual Studio C++ 2008 Express Edition」をダウンロードするなどして入手してください。
この連載は基本的にWindows環境を想定して解説しますが、インテルTBBそのものは他のOS上でも動作しますので、適宜読み替えて参考にしてください。
そのほかに、インテルTBBを使うための準備が必要なので次項で解説します。
インテルTBBの準備
インテルTBBを使用する方法は2つあります。1つ目は「インテル Parallel Studio」を使用することです。インテル Parallel StudioはインテルTBBがあらかじめ用意されており、オプションを指定するだけでインテルTBBを使用できます。詳しくは『インテル Parallel Studioを使って並列化プログラミングを試してみた』(Codezine)を参照ください。
2つ目の方法は、オープンソース版のインテルTBBを公式ホームページからダウンロードしてきて、ライブラリとインクルードファイルへのパスを指定することです。これからその手順を解説します。ただし、この記事は2009年10月に書かれたものであり、今後URLが変更される恐れがありますのでその点に注意してください。
まずは公式ホームページにアクセスし、[Downloads]と書かれたタグを選択します。そして、[Stable Release]という見出しをクリックすると、インテルTBBのいくつかのバージョンが表示されます。
ここでは「tbb22_20090809oss」を選択し、クリックするものとして話を進めます。画面下部の「tbb22_20090809oss_win.zip」と書かれた右横にある[Download]リンクをクリックすると、ダウンロードが始まります。ダウンロード後は、好きな場所に解凍してください。
続いてVisual Studio(以下、VS)でパスの設定を行います。[ツール]-[オプション]-[プロジェクトおよびソリューション]-[VC++ディレクトリィ]を選択すると、「ディレクトリィを表示するプロジェクト」という項目があるので、そこでインクルードファイルを選択してから、先ほどダウンロードしたTBBのパスを追加します。例えば、Cドライブに解凍したと仮定するとパスは「C:\tbb22_20090809oss\include」になります。このパスを追加したら、同様にして「ライブラリ」と「ソースコード」のパスを追加してください。
以上で設定は完了です。これでインテルTBBを使う準備が整いました。
並列処理とコンテナ:スレッドセーフとは何か
並列プログラミングを行う際には、従来の逐次プログラミングでは考えなかったことを考えなくてはなりません。そのうちの1つが、並列処理で同時にコンテナを操作する時に起こる問題についてです。これはよく「マルチスレッドプログラミングではスレッドセーフなコンテナを使う必要がある」と表現されます。しかし、スレッドセーフとは何なのかがよくわからない人も多いかと思います。それは無理もないことなので、気にする必要はありません。この項ではそのことについて解説します。
スレッドセーフでないコンテナを使用すると何が問題なのかを理解するためには、実際にシンプルなコンテナを作るのが1番です。そうすれば、何が問題なのかが分かります。これからスレッドセーフでない極力シンプルにしたListを実装するサンプルプロジェクトSimpleList
を掲載します。まずはそのコードを見てください。
#include <iostream> using namespace std; /*------------------------------------------------------------------------------- 0以上の数値のみを格納するシンプルなリスト ----------------------------------------------------------------------------------*/ class List { private: int size; int index; int* buffer; public: /*------------------------------------------------------- リストのサイズを指定して初期化 ---------------------------------------------------------*/ List( int size ) { this->size = size; this->buffer = new int[ size ]; this->index = -1; for ( int i = 0; i < this->size; ++i ) this->buffer[ i ] = -1; } /*------------------------------------------------------- バッファを削除する ---------------------------------------------------------*/ ~List() { delete[] buffer; } /*------------------------------------------------------- リストに存在する要素の個数 ---------------------------------------------------------*/ int Count() { return this->index + 1; } /*------------------------------------------------------- データを追加する 戻り値は成功時0・失敗時-1 ---------------------------------------------------------*/ int Add( int data ) { if ( index < size && data >= 0 ) { this->index++; this->buffer[ index ] = data; return 0; } else { return -1; } } /*------------------------------------------------------- 指定した位置にある要素を削除する 戻り値は成功時0・失敗時-1 ---------------------------------------------------------*/ int Remove( int pos ) { if ( pos <= index && pos >= 0 ) { //データを1つ左へ移動して要素を消す for ( int i = pos; i < index; ++i ) { this->buffer[ i ] = this->buffer[ i + 1 ]; } this->buffer[ this->index ] = -1; //未使用領域を初期化 this->index--; return 0; } else { return -1; } } //指定された位置の要素を返す int& operator[] ( int pos ) { if ( pos <= index && pos >= 0 ) { return this->buffer[ pos ]; } else { cout << "境界エラーが発生しました。" << endl; exit( 1 ); } } }; /*------------------------------------------------------------------------------- メインプログラム ----------------------------------------------------------------------------------*/ int main(void) { //0~9の数値を格納して表示 int size = 10; List list( size ); for ( int i = 0; i < size; ++i ) { list.Add( i ); } cout << "リストのサイズは" << list.Count() << "です。" << endl; cout << "格納されている要素は・・・" << endl; for ( int i = 0; i < list.Count(); ++i ) { cout << list[ i ] << " "; } cout << endl << endl; //要素をひとつ削除して表示 int pos = 0; cout << "インデック" << pos << "の要素を削除します。" << endl; int result = list.Remove( pos ); if ( result == -1 ) { cout << "存在しない要素を削除しようとしました。インデックスの指定が間違っています。" << endl; exit( 1 ); } cout << "リストのサイズは" << list.Count() << "です。" << endl; cout << "格納されている要素は・・・" << endl; for ( int i = 0; i < list.Count(); ++i ) { cout << list[ i ] << " "; } cout << endl << endl; return 0; }
サンプルコードSimpleList
は、0以上の数値しか格納できないリストに対して、0~10の数値をリストに追加してから0を削除しています。削除はバッファ内のデータを左(インデックスが低い方)に1つずらして上書きし、最後の位置を-1で初期化することにより実現しています。サンプルを図示すると下記のとおりになります。
SimpleList
サンプルプロジェクトを実行してみてください。このように、逐次プログラミングをしている時は、問題は生じません。しかし、並列プログラミングを行った時、問題が生じます。そのことを次項のSimpleList2
サンプルプロジェクトで示します。