並列コンテナーと並列オブジェクト
並列パターン ライブラリ (PPL) には、要素へのスレッド セーフなアクセスを提供するコンテナーとオブジェクトがいくつか含まれています。
コンカレント コンテナーは、最も重要な操作へのコンカレンシー セーフなアクセスを提供します。 ここでは、コンカレンシー セーフは、ポインターまたは反復子が常に有効であることを意味します。 要素の初期化または特定の走査順序が保証されるわけではありません。 これらのコンテナーの機能は、C++ 標準ライブラリで提供される機能と似た機能です。 たとえば、concurrency::concurrent_vector クラスは std::vector クラスと似ています。ただし、concurrent_vector
クラスでは要素を並列で追加できます。 同じコンテナーへの読み取りアクセスと書き込みアクセスの両方を必要とする並列コードがある場合は、コンカレント コンテナーを使用します。
コンカレント オブジェクトは、コンポーネント間で同時に共有されます。 コンカレント オブジェクトの状態を並列で計算するプロセスでは、同じ状態を連続して計算する別のプロセスと同じ結果が生成されます。 concurrency::combinable クラスは、コンカレント オブジェクト型の 1 つの例です。 combinable
クラスを使用すると、計算を並列で実行し、それらの計算を最終的な結果に結合できます。 ミューテックスなどの同期メカニズムを使用する場合にはコンカレント オブジェクトを使用して、共有変数またはリソースへのアクセスを同期します。
セクション
このトピックでは、次の並列コンテナーと並列オブジェクトについて詳しく説明します。
コンカレント コンテナー:
コンカレント オブジェクト:
concurrent_vector クラス
concurrency::concurrent_vector クラスは、std::vector クラスと同様に、要素にランダムにアクセスできるシーケンス コンテナー クラスです。 concurrent_vector
クラスを使用すると、コンカレンシーセーフな追加操作と要素アクセス操作が可能になります。 追加操作は、既存のポインターまたは反復子を無効にしません。 反復子アクセスと走査操作もコンカレンシーセーフです。 ここでは、コンカレンシー セーフは、ポインターまたは反復子が常に有効であることを意味します。 要素の初期化または特定の走査順序が保証されるわけではありません。
concurrent_vector と vector の違い
concurrent_vector
クラスは vector
クラスによく似ています。 concurrent_vector
オブジェクトに対する追加、要素アクセス、反復子のアクセス操作の複雑さは、vector
オブジェクトの場合と同じです。 次に concurrent_vector
と vector
の違いを示します。
concurrent_vector
オブジェクトの追加、要素アクセス、反復子アクセス、反復子走査操作はコンカレンシーセーフです。concurrent_vector
オブジェクトの末尾にのみ要素を追加できます。concurrent_vector
クラスは、insert
メソッドを提供しません。concurrent_vector
オブジェクトは、これに追加する場合、移動セマンティクスを使用しません。concurrent_vector
クラスは、erase
メソッドまたはpop_back
メソッドを提供しません。vector
と同様に、クリア メソッドを使用して、concurrent_vector
オブジェクトからすべての要素を削除します。concurrent_vector
クラスは、その要素をメモリに連続して格納しません。 したがって、配列を使用できるすべての方法でconcurrent_vector
クラスを使用することはできません。 たとえば、concurrent_vector
型のv
という名前の変数では、式&v[0]+2
の動作は不定になります。concurrent_vector
クラスは、grow_by メソッドと grow_to_at_least メソッドを定義します。 これらのメソッドは、コンカレンシーセーフである場合を除き、resize メソッドに似ています。concurrent_vector
オブジェクトに追加したりサイズを変更したりすると、このオブジェクトは要素を再配置しません。 これにより、既存のポインターと反復子をコンカレント操作中も有効なままです。このランタイムは、
bool
型に対してconcurrent_vector
の特殊化バージョンを定義しません。
コンカレンシーセーフ操作
concurrent_vector
オブジェクトのサイズを追加または増やす、または concurrent_vector
オブジェクト内の要素にアクセスするメソッドは、すべてコンカレンシーセーフです。 ここでは、コンカレンシー セーフは、ポインターまたは反復子が常に有効であることを意味します。 要素の初期化または特定の走査順序が保証されるわけではありません。 この規則の例外は、resize
メソッドです。
次の表は、コンカレンシーセーフな一般的な concurrent_vector
メソッドと演算子を示しています。
ランタイムが C++ 標準ライブラリとの互換性のために提供する操作 (reserve
など) は、コンカレンシーセーフではありません。 次の表は、コンカレンシーセーフではない一般的なメソッドと演算子を示しています。
既存の要素の値を変更する操作は、コンカレンシーセーフではありません。 reader_writer_lock オブジェクトなどの同期オブジェクトを使用して、コンカレント読み取り操作と書き込み操作を同じデータ要素に同期します。 同期オブジェクトの詳細については、「同期データ構造」をご覧ください。
vector
を使用して concurrent_vector
を使用する既存のコードを変換すると、コンカレント操作によってアプリケーションの動作が変更される可能性があります。 たとえば、concurrent_vector
オブジェクトに対して 2 つのタスクを同時に実行する次のプログラムを考えてみましょう。 最初のタスクは、concurrent_vector
オブジェクトに追加の要素を追加します。 2 番目のタスクは、同じオブジェクト内のすべての要素の合計を計算します。
// parallel-vector-sum.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_vector.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
// Create a concurrent_vector object that contains a few
// initial elements.
concurrent_vector<int> v;
v.push_back(2);
v.push_back(3);
v.push_back(4);
// Perform two tasks in parallel.
// The first task appends additional elements to the concurrent_vector object.
// The second task computes the sum of all elements in the same object.
parallel_invoke(
[&v] {
for(int i = 0; i < 10000; ++i)
{
v.push_back(i);
}
},
[&v] {
combinable<int> sums;
for(auto i = begin(v); i != end(v); ++i)
{
sums.local() += *i;
}
wcout << L"sum = " << sums.combine(plus<int>()) << endl;
}
);
}
end
メソッドはコンカレンシーセーフですが、push_back メソッドのコンカレント呼び出しを行うと、end
によって返される値が変更されます。 反復子が走査する要素の数は不確定です。 そのため、このプログラムは、実行するたびに結果が変わる可能性があります。 要素型が簡単でない場合、push_back
と end
の呼び出しの間に競争状態が発生する可能性があります。 end
メソッドは、割り当てられているが、完全には初期化されていない要素を返す場合があります。
例外の安全性
増加操作または割り当て操作で例外がスローされた場合、concurrent_vector
オブジェクトの状態は無効になります。 無効な状態にある concurrent_vector
オブジェクトの動作は、特に明記されていない限り不定になります。 ただし、デストラクターは、オブジェクトが無効な状態でも、オブジェクトが割り当てるメモリを常に解放します。
ベクター要素のデータ型 T
は、次の要件を満たしている必要があります。 満たしていない場合、concurrent_vector
クラスの動作は不定になります。
デストラクターはスローしない必要があります。
既定のコンストラクターまたはコピーコンストラクターがスローした場合、デストラクターは
virtual
キーワードを使用して宣言する必要はありません。また、ゼロ初期化メモリで正しく動作する必要があります。
[トップ]
concurrent_queue クラス
concurrency::concurrent_queue クラスは、std::queue クラスと同様にフロント要素とバック要素にアクセスできます。 concurrent_queue
クラスを使用すると、コンカレンシーセーフなエンキュー操作とデキュー操作が可能になります。 ここでは、コンカレンシー セーフは、ポインターまたは反復子が常に有効であることを意味します。 要素の初期化または特定の走査順序が保証されるわけではありません。 concurrent_queue
クラスは、コンカレンシーセーフではない反復子のサポートも提供します。
concurrent_queue と queue の違い
concurrent_queue
クラスは queue
クラスによく似ています。 次に concurrent_queue
と queue
の違いを示します。
concurrent_queue
オブジェクトに対するエンキュー操作とデキュー操作は、コンカレンシーセーフです。concurrent_queue
クラスは、コンカレンシーセーフではない反復子のサポートも提供します。concurrent_queue
クラスは、front
メソッドまたはpop
メソッドを提供しません。concurrent_queue
クラスは、try_pop メソッドを定義することで、これらのメソッドを置き換えます。concurrent_queue
クラスは、back
メソッドを提供しません。 したがって、キューの末尾を参照することはできません。concurrent_queue
クラスは、size
メソッドの代わりに unsafe_size メソッドを提供します。unsafe_size
メソッドはコンカレンシーセーフではありません。
コンカレンシーセーフ操作
concurrent_queue
オブジェクトに対してエンキューまたはデキューを行うすべてのメソッドは、コンカレンシーセーフです。 ここでは、コンカレンシー セーフは、ポインターまたは反復子が常に有効であることを意味します。 要素の初期化または特定の走査順序が保証されるわけではありません。
次の表は、コンカレンシーセーフな一般的な concurrent_queue
メソッドと演算子を示しています。
empty
メソッドはコンカレンシーセーフですが、コンカレント操作によって empty
メソッドが戻る前にキューが拡大または縮小する場合があります。
次の表は、コンカレンシーセーフではない一般的なメソッドと演算子を示しています。
反復子のサポート
concurrent_queue
は、コンカレンシーセーフではない反復子を提供します。 これらの反復子は、デバッグのためにのみ使用することをお勧めします。
concurrent_queue
反復子は、前方方向にのみ要素を走査します。 次の表は、各反復子がサポートする演算子を示しています。
Operator | 説明 |
---|---|
operator++ |
キューの次の項目に進みます。 この演算子は、前置インクリメントと後置インクリメントの両方のセマンティクスを提供するためにオーバーロードされています。 |
operator* |
現在の項目への参照を取得します。 |
operator-> |
現在の項目へのポインターを取得します。 |
[トップ]
concurrent_unordered_map クラス
concurrency::concurrent_unordered_map クラスは、std::unordered_map クラスと同様の連想コンテナー クラスであり、std::pair<const Key 型 Ty> の要素の可変長シーケンスを制御します。 順序なしのマップは、キーと値のペアを追加したり、キーで値を参照したりできるディクショナリと考えることができます。 このクラスは、共有コンテナーに同時にアクセスしたり、それに挿入したり、更新したりする必要がある複数のスレッドまたはタスクがある場合に便利です。
次の例は、concurrent_unordered_map
を使用するための基本的な構造を示しています。 この例では、['a', 'i'] の範囲内に文字キーを挿入します。 操作の順序は不明であるため、各キーの最終的な値も不定になります。 ただし、挿入を並列で実行するのは安全です。
// unordered-map-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_map.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
//
// Insert a number of items into the map in parallel.
concurrent_unordered_map<char, int> map;
parallel_for(0, 1000, [&map](int i) {
char key = 'a' + (i%9); // Geneate a key in the range [a,i].
int value = i; // Set the value to i.
map.insert(make_pair(key, value));
});
// Print the elements in the map.
for_each(begin(map), end(map), [](const pair<char, int>& pr) {
wcout << L"[" << pr.first << L", " << pr.second << L"] ";
});
}
/* Sample output:
[e, 751] [i, 755] [a, 756] [c, 758] [g, 753] [f, 752] [b, 757] [d, 750] [h, 754]
*/
concurrent_unordered_map
を使用してマップ操作と縮小操作を並列で実行する例については、「方法: マップ操作と縮小操作を並列実行する」をご覧ください。
concurrent_unordered_map と unordered_map の違い
concurrent_unordered_map
クラスは unordered_map
クラスによく似ています。 次に concurrent_unordered_map
と unordered_map
の違いを示します。
erase
、bucket
、bucket_count
、およびbucket_size
メソッドは、それぞれ、unsafe_erase
、unsafe_bucket
、unsafe_bucket_count
、およびunsafe_bucket_size
という名前が付いています。unsafe_
名前付け規則では、これらのメソッドがコンカレンシーセーフではないことを示しています。 コンカレンシーセーフの詳細については、「コンカレンシーセーフ操作」をご覧ください。挿入操作では、既存のポインターまたは反復子を無効にしたり、マップ内に既に存在する項目の順序を変更したりすることはしません。 挿入操作と走査操作は同時に実行できます。
concurrent_unordered_map
は前方反復のみをサポートしています。挿入は、
equal_range
によって返される反復子を無効にしたり、更新したりすることはありません。 挿入では、範囲の末尾に等しくない項目を追加できます。 begin 反復子が等しい項目を指しています。
デッドロックを回避するために、メモリ アロケーター、ハッシュ関数、またはその他のユーザー定義コードを呼び出すときに、concurrent_unordered_map
にはロックを保持する方法はありません。 また、ハッシュ関数が同じ値に対して同じキーを常に評価する必要があります。 最適なハッシュ関数は、ハッシュ コード空間全体でキーを均等に分散します。
コンカレンシーセーフ操作
concurrent_unordered_map
クラスを使用すると、コンカレンシーセーフな挿入操作と要素アクセス操作が可能になります。 挿入操作は、既存のポインターまたは反復子を無効にしません。 反復子アクセスと走査操作もコンカレンシーセーフです。 ここでは、コンカレンシー セーフは、ポインターまたは反復子が常に有効であることを意味します。 要素の初期化または特定の走査順序が保証されるわけではありません。 次の表は、コンカレンシーセーフな一般的に使用される concurrent_unordered_map
メソッドと演算子を示しています。
count
メソッドは同時に実行されるスレッドから安全に呼び出すことができますが、新しい値が同時にコンテナーに挿入されると、異なるスレッドが異なる結果を受け取ることがあります。
次の表は、コンカレンシーセーフではない一般的に使用されるメソッドと演算子を示しています。
これらのメソッドに加えて、unsafe_
で始まるメソッドもコンカレンシーセーフではありません。
[トップ]
concurrent_unordered_multimap クラス
concurrency::concurrent_unordered_multimap クラスは、複数の値を同じキーにマップできることを除けば、concurrent_unordered_map
クラスによく似ています。 これは次の点で concurrent_unordered_map
と異なります。
concurrent_unordered_multimap::insert メソッドは、
std::pair<iterator, bool>
の代わりに反復子を返します。concurrent_unordered_multimap
クラスは、operator[]
メソッドもat
メソッドも提供しません。
次の例は、concurrent_unordered_multimap
を使用するための基本的な構造を示しています。 この例では、['a', 'i'] の範囲内に文字キーを挿入します。 concurrent_unordered_multimap
を使用すると、キーは複数の値を持つことができます。
// unordered-multimap-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_map.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
//
// Insert a number of items into the map in parallel.
concurrent_unordered_multimap<char, int> map;
parallel_for(0, 10, [&map](int i) {
char key = 'a' + (i%9); // Geneate a key in the range [a,i].
int value = i; // Set the value to i.
map.insert(make_pair(key, value));
});
// Print the elements in the map.
for_each(begin(map), end(map), [](const pair<char, int>& pr) {
wcout << L"[" << pr.first << L", " << pr.second << L"] ";
});
}
/* Sample output:
[e, 4] [i, 8] [a, 9] [a, 0] [c, 2] [g, 6] [f, 5] [b, 1] [d, 3] [h, 7]
*/
[トップ]
concurrent_unordered_set クラス
concurrency::concurrent_unordered_set クラスは、キーと値のペアの代わりに値を管理する点を除いて concurrent_unordered_map
クラスによく似ています。 concurrent_unordered_set
クラスは、operator[]
メソッドも at
メソッドも提供しません。
次の例は、concurrent_unordered_set
を使用するための基本的な構造を示しています。 この例では、['a', 'i'] の範囲内に文字値を挿入します。 挿入を並列で実行するのは安全です。
// unordered-set-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_set.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
//
// Insert a number of items into the set in parallel.
concurrent_unordered_set<char> set;
parallel_for(0, 10000, [&set](int i) {
set.insert('a' + (i%9)); // Geneate a value in the range [a,i].
});
// Print the elements in the set.
for_each(begin(set), end(set), [](char c) {
wcout << L"[" << c << L"] ";
});
}
/* Sample output:
[e] [i] [a] [c] [g] [f] [b] [d] [h]
*/
[トップ]
concurrent_unordered_multiset クラス
concurrency::concurrent_unordered_multiset クラスは、重複する値が許可されている点を除いて、concurrent_unordered_set
クラスによく似ています。 これは次の点で concurrent_unordered_set
と異なります。
concurrent_unordered_multiset::insert メソッドは、
std::pair<iterator, bool>
の代わりに反復子を返します。concurrent_unordered_multiset
クラスは、operator[]
メソッドもat
メソッドも提供しません。
次の例は、concurrent_unordered_multiset
を使用するための基本的な構造を示しています。 この例では、['a', 'i'] の範囲内に文字値を挿入します。 concurrent_unordered_multiset
を使用すると、値の複数回出現が可能になります。
// unordered-set-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_set.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
//
// Insert a number of items into the set in parallel.
concurrent_unordered_multiset<char> set;
parallel_for(0, 40, [&set](int i) {
set.insert('a' + (i%9)); // Geneate a value in the range [a,i].
});
// Print the elements in the set.
for_each(begin(set), end(set), [](char c) {
wcout << L"[" << c << L"] ";
});
}
/* Sample output:
[e] [e] [e] [e] [i] [i] [i] [i] [a] [a] [a] [a] [a] [c] [c] [c] [c] [c] [g] [g]
[g] [g] [f] [f] [f] [f] [b] [b] [b] [b] [b] [d] [d] [d] [d] [d] [h] [h] [h] [h]
*/
[トップ]
combinable クラス
concurrency::combinable クラスには、再利用可能なスレッド ローカル ストレージが用意されており、詳細な計算を実行した後、その計算を最終結果にマージできます。 combinable
オブジェクトは減少変数と考えることができます。
combinable
クラスは、複数のスレッドまたはタスク間で共有されるリソースがあるときに便利です。 combinable
クラスは、共有リソースへのロックフリー アクセスを提供することによって、共有状態を回避することができます。 したがって、このクラスは、複数のスレッドからの共有データへのアクセスを同期するために、ミューテックスなどの同期メカニズムを使用する代わりに使用できます。
メソッドと機能
次の表に、combinable
クラスの重要なメソッドの一部を示します。 すべての combinable
クラス メソッドの詳細については、「combinable クラス」をご覧ください。
Method | 説明 |
---|---|
local | 現在のスレッド コンテキストに関連付けられているローカル変数への参照を取得します。 |
clear | combinable オブジェクトからすべてのスレッドローカル変数を削除します。 |
combine combine_each |
指定された結合関数を使用して、すべてのスレッドローカル計算のセットから最終的な値を生成します。 |
combinable
クラスは、最終的にマージされた結果でパラメーター化されるテンプレート クラスです。 既定のコンストラクターを呼び出す場合、T
テンプレート パラメーターの型には、既定のコンストラクターとコピー コンストラクターが必要です。 T
テンプレート パラメーターの型に既定のコンストラクターが存在しない場合は、初期化関数をパラメーターとして受け取るコンストラクターのオーバーロードされたバージョンを呼び出します。
combine メソッドまたは combine_each メソッドを呼び出した後、combinable
オブジェクトに追加のデータ格納できます。 combine
メソッドと combine_each
メソッドを複数回呼び出す方法も可能です。 combinable
オブジェクト内で変更されたローカル値がない場合、combine
メソッドと combine_each
メソッドは、呼び出されるごとに同じ結果を生成します。
例
combinable
クラスの使用例については、次のトピックを参照してください。
[トップ]
関連項目
方法: 並列コンテナーを使用して効率を向上させる
並列コンテナーを使用して、データの格納とアクセスを並行して効率的に行う方法について説明します。
方法: combinable を使用してパフォーマンスを向上させる
combinable
クラスを使用して共有状態を回避し、それによってパフォーマンスを向上させる方法を示します。
方法: combinable を使用して集合を結合する
combine
関数を使用してスレッドローカルのデータ セットをマージする方法を示します。
並列パターン ライブラリ (PPL)
同時実行アプリケーションの開発に不可欠な、スケーラビリティが高く使いやすいプログラミング モデルを提供する PPL について説明します。
リファレンス
concurrent_unordered_multimap クラス