次の方法で共有


並列コンテナーと並列オブジェクト

並列パターン ライブラリ (PPL: Parallel Patterns Library) には、要素へのスレッド セーフなアクセスを提供するいくつかのコンテナーとオブジェクトが含まれています。

同時実行コンテナーは、最も重要な操作への安全な同時実行セーフ アクセスを提供します。 これらのコンテナーの機能は、標準テンプレート ライブラリ (STL: Standard Template Library) が提供するコンテナーに似ています。 たとえば、concurrency::concurrent_vector クラスは std::vector クラスに似ていますが、concurrent_vector クラスは、要素を並列に付けることができます。 同じコンテナーへの読み取りアクセスと書き込みアクセスの両方を必要とする並列コードがある場合、同時実行コンテナーを使用します。

同時実行オブジェクトは、コンポーネント間で同時に共有されます。 同時実行オブジェクトの状態を並行して計算するプロセスでは、同じ状態を連続して計算するプロセスと同じ結果が生成されます。 concurrency::combinable クラスは、同時実行オブジェクトの 1 例です。 combinable クラスを使用すると、計算を並行して実行した後、その計算を最終結果に結合できます。 同期機構 (ミューテックスなど) を使用して共有変数またはリソースへのアクセスを同期する場合、同時実行オブジェクトを使用します。

セクション

ここでは、次の並列コンテナーと並列オブジェクトについて詳しく説明します。

同時実行コンテナー:

  • concurrent_vector クラス

    • concurrent_vector と vector の違い

    • 同時実行セーフな操作

    • 例外セーフ

  • concurrent_queue クラス

    • concurrent_queue と queue の違い

    • 同時実行セーフな操作

    • 反復子のサポート

  • concurrent_unordered_map クラス

    • concurrent_unordered_map と unordered_map の違い

    • 同時実行セーフな操作

  • concurrent_unordered_multimap クラス

  • concurrent_unordered_set クラス

  • concurrent_unordered_multiset クラス

同時実行オブジェクト:

  • combinable クラス

    • メソッドおよび機能

concurrent_vector クラス

concurrency::concurrent_vector クラスは std::vector クラスと同様に、要素にランダムにアクセスできるようにするシーケンス コンテナー クラスです。 concurrent_vector クラスを使用すると、同時実行セーフの追加操作と要素アクセス操作を実行できます。 追加操作では、既存のポインターまたは反復子は無効化されません。 反復子のアクセス操作と走査操作も同時実行セーフです。

concurrent_vector と vector の違い

concurrent_vector クラスは、vector クラスとよく似ています。 concurrent_vector オブジェクトに対する追加、要素アクセス、および反復子アクセスの各操作の複雑さは、vector オブジェクトの場合と同じです。 concurrent_vectorvector の違いを次に示します。

  • concurrent_vector オブジェクトに対する追加、要素アクセス、反復子アクセス、および反復子走査の各操作は、同時実行セーフです。

  • 要素を追加できるのは、concurrent_vector オブジェクトの末尾だけです。 concurrent_vector クラスには insert メソッドは用意されていません。

  • concurrent_vector オブジェクトでは、ユーザーが追加操作を行っても、移動セマンティクスは使用されません。

  • concurrent_vector クラスには erase メソッドまたは pop_back メソッドは用意されていません。 vector と同様に、clear メソッドを使用して、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 オブジェクトのサイズを増やすメソッド、または concurrent_vector オブジェクト内の要素にアクセスするメソッドはすべて、同時実行セーフです。 この規則の例外は、resize メソッドです。

同時実行セーフである一般的な concurrent_vector メソッドと演算子を次の表に示します。

at

End

operator[]

begin

front

push_back

back

grow_by

rbegin

capacity

grow_to_at_least

rend

empty

max_size

size

ランタイムは、STL の互換性、たとえば、reserveに与える操作は、同時実行セーフではありません。 同時実行セーフではない一般的なメソッドと演算子を次の表に示します。

assign

reserve

clear

resize

operator=

shrink_to_fit

既存の要素の値を変更する操作は、同時実行セーフではありません。 同じデータ要素に対する同時読み取り/書き込み操作を同期するには、同期オブジェクト (reader_writer_lock オブジェクトなど) を使用します。 同期オブジェクトの詳細については、「同期データ構造」を参照してください。

vector を使用する既存のコードを、concurrent_vector を使用するように変換すると、アプリケーションの動作が変化します。 たとえば、concurrent_vector オブジェクトに対して 2 つのタスクを同時に実行する次のプログラムについて考えます。 1 つ目のタスクでは、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 によって返される値が変更されます。 反復子が走査する要素の数は不確定です。 したがって、このプログラムを実行するたびに、異なる結果が生成されることがあります。

例外セーフ

拡張演算または代入演算が例外をスローする場合、concurrent_vector オブジェクトの状態は無効になります。 特に指定がない限り、無効な状態の concurrent_vector オブジェクトの動作は未定義です。 ただし、オブジェクトが無効な状態であっても、デストラクターはオブジェクトが割り当てるメモリを解放します。

ベクター要素のデータ型 (_Ty) は、次の要件を満たしている必要があります。 それ以外の場合、concurrent_vector クラスの動作は未定義です。

  • デストラクターからはスローしないでください。

  • 既定のコンストラクターまたはコピー コンストラクターからスローする場合、virtual キーワードを使用してデストラクターを宣言しないでください。デストラクターは、ゼロで初期化されたメモリで正常に動作します。

[トップ]

concurrent_queue クラス

std::queue クラスなど concurrency::concurrent_queue クラスは、単に、フロントとバックの要素にアクセスすることができます。 concurrent_queue クラスを使用すると、同時実行セーフなキューへの登録操作とキューからの削除操作を実行できます。 concurrent_queue クラスでは、同時実行セーフではない反復子もサポートされています。

concurrent_queue と queue の違い

concurrent_queue クラスは、queue クラスとよく似ています。 concurrent_queuequeue の違いを次に示します。

  • 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 オブジェクトからキューを削除するメソッドはすべて、同時実行セーフです。

同時実行セーフである一般的な concurrent_queue メソッドと演算子を次の表に示します。

empty

push

get_allocator

try_pop

empty メソッドは同時実行セーフですが、同時実行操作により、empty メソッドが返される前に、キューが拡張または縮小される可能性があります。

同時実行セーフではない一般的なメソッドと演算子を次の表に示します。

clear

unsafe_end

unsafe_begin

unsafe_size

反復子のサポート

concurrent_queue には、同時実行セーフではない反復子が用意されています。 これらの反復子は、デバッグのためにのみ使用することをお勧めします。

concurrent_queue 反復子は、前方にのみ要素を走査します。 次の表に、各反復子がサポートする演算子を示します。

演算子

説明

operator++

キュー内の次の項目に進みます。 この演算子は、前置インクリメントと後置インクリメントの両方のセマンティクスを提供するためにオーバーロードされます。

operator*

現在の項目への参照を取得します。

operator->

現在の項目へのポインターを取得します。

[トップ]

concurrent_unordered_map クラス

concurrency::concurrent_unordered_map クラスは std::unordered_map クラスと同様に、型、std::pair<const Key、Ty>要素の可変長シーケンスを制御する連想コンテナー クラスです。 キーと値のペアを追加するか、キーで値を検索することができます。辞書として順序なしのマップに注意してください。 このクラスは、同時に共有コンテナーにアクセスし、それに挿入するか、それを更新しなければならないタスクまたはスレッドが複数ある場合に便利です。

次の例では concurrent_unordered_mapを使用するための基本的な構造を示します。 この例では、範囲[「I " a "]で文字キーを挿入します。 操作の順序は不定であるため、各キーの最終値は、不定になります。 ただし、挿入を並列実行する方が安全です。

// 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_mapunordered_map の違いを次に示します。

  • erasebucketbucket_countbucket_size のメソッドは、unsafe_eraseunsafe_bucketunsafe_bucket_countunsafe_bucket_sizeという名前です。 unsafe_ の名前付け規則は、これらのメソッドは同時実行セーフではないことを示します。 同時実行の安全性に関する詳細については、「同時実行セーフな操作」を参照してください。

  • 挿入操作、既存のポインターまたは反復子を無効にして、変更するマップに既に存在する項目の順序を示します。 挿入するとも操作は同時に行われます。

  • concurrent_unordered_map サポートはイテレーションだけを転送します。

  • Insert は equal_rangeが返す反復子を無効にしたり、更新しません。 Insert は範囲の末尾に等しくない項目を追加できます。 開始の反復子は、等しい項目を指します。

デッドロックを回避するには concurrent_unordered_map のメモリ アロケーター メソッドは、ハッシュ関数、または他のユーザー定義コードを呼び出すときにロックを保持できません。 また、ハッシュ関数を同じ値に等しいキーを常に評価する必要があります。 最適なハッシュ関数は、ハッシュ コード領域でキーを一様に分布します。

同時実行セーフな操作

concurrent_unordered_map クラスを使用すると、同時実行セーフの追加操作と要素アクセス操作を実行できます。 挿入操作、既存のポインターまたは反復子は無効になりません。 反復子のアクセス操作と走査操作も同時実行セーフです。 次の表は、同時実行セーフである演算子と concurrent_unordered_map でよく使用されるメソッドを示しています。

at

count

find

Key

begin

empty

get_allocator

max_size

cbegin

end

hash_function

operator[]

cend

equal_range

insert

size

count のメソッドが実行中のスレッドから同時に安全に呼び出すことができますが、別のスレッドが新しい値がコンテナーに同時に挿入して、結果を受け取ることができます。

次の表は、同時実行セーフではない演算子、一般的に使用するメソッドを示しています。

clear

max_load_factor

rehash

load_factor

operator=

swap

これらのメソッドに加えて、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を使用するための基本的な構造を示します。 この例では、範囲[「I " a "]で文字キーを挿入します。 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を使用するための基本的な構造を示します。 この例では、範囲[「I " a "]で文字値を挿入します。 挿入を並列実行する方が安全です。

// 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を使用するための基本的な構造を示します。 この例では、範囲[「I " a "]で文字値を挿入します。 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 クラス」を参照してください。

方法

説明

local

現在のスレッド コンテキストに関連付けられているローカル変数への参照を取得します。

clear

combinable オブジェクトからすべてのスレッド ローカル変数を削除します。

combine

combine_each

指定された combine 関数を使用して、すべてのスレッド ローカル計算のセットから最終値を生成します。

combinable クラスは、マージされた最終結果でパラメーター化されたテンプレート クラスです。 既定のコンストラクターを呼び出す場合、_Ty テンプレート パラメーター型には既定のコンストラクターとコピー コンストラクターが必要です。 _Ty テンプレート パラメーター型に既定のコンストラクターがない場合は、パラメーターとして初期化関数を使用するコンストラクターのオーバーロードされたバージョンを呼び出します。

combine メソッドまたは combine_each メソッドを呼び出した後、追加データを combinable オブジェクトに格納できます。 combine メソッドと combine_each メソッドを複数回呼び出すこともできます。 combinable オブジェクト内のローカル値が変更されない場合、combine メソッドと combine_each メソッドは、呼び出されるたびに同じ結果になります。

combinable クラスの使用方法の例については、次のトピックを参照してください。

[トップ]

関連トピック

参照

concurrent_vector クラス

concurrent_queue クラス

concurrent_unordered_map クラス

concurrent_unordered_multimap クラス

concurrent_unordered_set クラス

concurrent_unordered_multiset クラス

combinable クラス