クラス:concurrent_hash_map< Key, T, HashCompare >
Intel TBBのテンプレートクラスconcurrent_hash_mapは、対応するSTLのコンテナに似ていますが、要素への同時アクセスを許します。これは、Key型のキーをT型の値にマップするハッシュテーブルです。特性型のHashCompareは、マッピングで使用するハッシュ関数と、2つのキーが等しいかどうか評価する関数を定義します。これらの関数は、等しい2つのキーから同じハッシュコードが生成されることに同意していなければならないので、一緒に使用されます。
文字列型のキーに対する特性クラスの例を以下に示します。
struct my_hash_compare { static size_t hash( const string& x ) { size_t h = 0; for( const char* s = x.c_str(); *s; ++s ) h = (h*17)^*s; return h; } //! True if strings are equal static bool equal( const string& x, const string& y ) { return x==y; };
concurrent_hash_mapは、std::pair<const Key,T>型の要素のコンテナとして機能します。一般にコンテナ要素にアクセスするのは、それを更新または読み取ろうとするときです。テンプレートクラスconcurrent_hash_mapは、この2つの目的を、それぞれaccessorクラスとconst_accessorクラスによってサポートします。これらはスマートポインタとして機能し、先ほどのSTLのmapの例にはなかった、要素へのアトミックなアクセスを可能にします。
accessorは更新(書き込み)アクセスを表します。これが要素を指している限り、テーブル内のそのキーを参照する他のすべての試みはaccessorが済むまでブロックします。const_accessorもよく似ていますが、こちらは「読み取り専用」アクセスを表しています。同時に複数のconst_accessorsが同じ要素を指すことが許されるので、要素が頻繁に読み取られ、あまり頻繁に更新されない状況では同時並列性が大幅に向上します。
concurrent_hash_map要素へのアクセスは、主としてinsert、find、eraseの各メソッドによって行います。findメソッドとinsertメソッドは、引数としてaccessorまたはconst_accessorを取ります。選択した引数によって、concurrent_hash_mapは「更新」と「読み取り専用」のどちらのアクセスが要求されているのかを判断します。メソッドが復帰すると、accessorまたはconst_acessorが破棄されるまで、そのアクセスが持続します。removeメソッドは書き込みアクセスを暗黙に要求します。従って、残存している他のアクセスが終了するのを待ち、それからキーを削除します。
次のコードは、concurrent_hash_mapに新しい要素を挿入する例です。
concurrent_hash_map<string, MyClass, my_hash_compare> string_table; void insert_into_string_table ( string &key, MyClass &m ) { // create an accessor that will act as a smart pointer // for write access concurrent_hash_map<string, MyClass, my_hash_compare>::accessor a; // call insert to create a new element, or return an existing // element if one exists. // accessor a locks this element for exclusive use by this thread string_table.insert( a, key ); // modify the value held by the pair a->second = m; // the accessor "a" releases the lock on the element when it is // destroyed at the end of the scope }
concurrent_queue< Key, T, HashCompare >
Intel TBBのテンプレートクラスconcurrent_queue<T>は、T型の値を持つ並列キューを実装しています。このキューに対しては、同時に複数のスレッドがプッシュ/ポップを行えます。このキューはデフォルトでは無制限ですが、最大容量を設定することで制限することができます。
一般にキューは先入れ先出し方式のデータ構造であり、シングルスレッドプログラムでは、この厳格な順序付けをサポートするようにキューを設計できます。しかし、同時に複数のスレッドがプッシュ/ポップを行う場合、何をもって「先」とするかはあまり明確でなくなります。Intel TBBのテンプレートクラスconcurrent_queueは、あるスレッドが2つの値をプッシュし、別のスレッドがこの2つの値をポップする場合に、これらの値がプッシュされたときと同じ順序でポップされることを保証します。異なるスレッドによってプッシュされる値のインタリーブは制限されません。
並列キューはプロデューサ-コンシューマアプリケーションでよく使われます。このようなアプリケーションでは、あるスレッドで生成されるデータが別のスレッドで消費されます。この種のアプリケーションを柔軟にサポートするため、Intel TBBはポップのブロッキングバージョンと非ブロッキングバージョンを提供しています。pop_if_presentメソッドは非ブロッキングです。このメソッドは値のポップを試みますが、キューが空なら、すぐに復帰します。それに対し、popメソッドはアイテムが利用可能になるまでブロックし、利用可能になったら、アイテムをキューからポップします。
次の例では、concurrent_queue<int>による2つのスレッド間の通信にブロッキングメソッドのpopを使用しています。Thread 1は、Thread 0によってキューにプッシュされた各値を、それらが利用可能になったときに出力します。
STLの大部分のコンテナと違って、concurrent_queue::size_typeは「符号付き」整数型であり、符号なしではありません。その理由は、concurrent_queue::size()が、開始されたプッシュ操作の数から開始されたポップ操作の数を差し引いたものとして定義されているからです。ポップの数がプッシュの数を上回ると、size()が負になります。例えば、concurrent_queueが空で、未決着のポップ操作がn個あるとすれば、size()は~nを返します。この方法によって、プロデューサはキューで待機しているコンシューマの数を簡単に知ることができます。
concurrent_vector< Key, T, HashCompare >
TBBで定義されている最後の並列コンテナはテンプレートクラスconcurrent_vectorです。concurrent_vectorは動的に拡大できる配列であり、拡大しながら同時にベクタ内の要素へのアクセスを可能にします。
concurrent_vectorクラスは安全な同時拡大を可能にするために、grow_byとgrow_to_at_leastという2つのメソッドを定義しています。grow_by(n)では、ベクタに連続するn個の要素を安全に追加することができ、追加した最初の要素のインデックスが返されます。各要素はT()で初期化されます。grow_to_at_least(n)は、ベクタの現在のサイズ(要素数)がn未満であれば、ベクタのサイズをnに拡大します。
次のルーチンは、共有ベクタにC文字列を安全に追加します。
void Append ( concurrent_vector<char>& vector, const char* string) { size_t n = strlen(string) + 1; memcpy( &vector[vector_grow_by(n)], string, n+1 ); }
size()メソッドはベクタ内の要素数を返します。これにはgrow_byメソッドとgrow_to_at_leastメソッドによって同時並列的に構築中の要素も含まれている可能性があります。従って、反復子がend()の現在値を超えない限り、concurrent_vectorの拡大中に反復子を使用しても大丈夫です。しかし、反復子が同時並列的に構築中の要素を参照している可能性があります。Intel TBBのconcurrent_vectorを使用するときに構築とアクセスを同期化するのは開発者の責任です。

