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

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

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

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

セクション

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

同時実行コンテナー:

  • concurrent_vector クラス

  • concurrent_queue クラス

同時実行オブジェクト:

  • 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 = v.begin(); i != v.end(); ++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 クラス

Concurrency::concurrent_queue クラスでは、std::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->

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

[ページのトップへ]

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 クラス

combinable クラス