コンカレンシー ランタイムの概要

このドキュメントでは、コンカレンシー ランタイムの概要について説明します。 また、コンカレンシー ランタイムの利点、使用する状況、コンポーネントどうしの対話方法、コンポーネントとオペレーティング システムやアプリケーションとの対話方法について説明します。

セクション

このドキュメントは、次のトピックに分かれています。

同時実行ランタイムの実装履歴

Visual Studio 2010 ~ 2013 で、同時実行ランタイムは msvcr100.dll~msvcr120.dll で組み込まれました。 UCRT リファクタリングが Visual Studio 2015 で発生した際、その DLL は次の 3 つの部分にリファクタリングされました:

  • ucrtbase.dll – C API。Windows 10 に含まれ、Windows Update を介してダウンレベルで利用されます。

  • vcruntime140.dll – コンパイラ サポート関数と EH ランタイム。Visual Studio に含まれます。

  • concrt140.dll – 同時実行ランタイム。Visual Studio に含まれます。 並列コンテナーおよびアルゴリズム (concurrency::parallel_for など) に必要です。 また STL では、同期プリミティブを支援するために、Windows XP でこの DLL を必要とします。Windows XP には条件変数がないためです。

Visual Studio 2015 以降では、コンカレンシー ランタイムのタスク スケジューラは、ppltasks.h 内の task クラスや関連する型のためのスケジューラではなくなりました。 現在、これらの型では、パフォーマンスや Windows 同期プリミティブとの相互運用性を向上させるために、Windows のスレッド プールが使用されています。

コンカレンシーのランタイムが重要である理由

コンカレンシー用のランタイムでは、同時に実行されるアプリケーションおよびアプリケーション コンポーネントに統一性と予測可能性が提供されます。 コンカレンシー ランタイムの利点の例として、協調タスク スケジューリング協調ブロッキングの 2 つがあります。

コンカレンシー ランタイムで使用される協調タスク スケジューラには、ワーク スティーリング アルゴリズムが実装されており、作業がコンピューティング リソース間に効率的に分散されます。 たとえば、同じランタイムによって管理される 2 つのスレッドを持つアプリケーションがあるとします。 一方のスレッドがスケジュールされたタスクを完了したら、他方のスレッドから作業をオフロードできます。 このメカニズムにより、アプリケーションの全体的な作業負荷のバランスが保たれます。

また、コンカレンシー ランタイムでは、協調ブロッキングを使用して、リソースへのアクセスを同期する同期プリミティブも提供されます。 たとえば、共有リソースへの排他アクセスを必要とするタスクがあるとします。 ランタイムは協調的なブロッキングによって、最初のタスクがリソースを待機しているときに、残りのクォンタムを使用して別のタスクを実行できます。 このメカニズムにより、コンピューティング リソースを最大限に利用できます。

[トップ]

アーキテクチャ

コンカレンシー ランタイムは、並列パターン ライブラリ (PPL)、非同期エージェント ライブラリ、タスク スケジューラ、およびリソース マネージャーの 4 つのコンポーネントで構成されます。 これらのコンポーネントは、オペレーティング システムとアプリケーションの間に配置されます。 次の図は、コンカレンシー ランタイムのコンポーネントがオペレーティング システムおよびアプリケーションとの間でどのようにやり取りするかを示しています。

コンカレンシー ランタイム アーキテクチャ

The Concurrency Runtime Architecture.

重要

タスク スケジューラとリソース マネージャーのコンポーネントは、ユニバーサル Windows プラットフォーム(UWP) アプリから、または ppltasks.h のタスク クラスなどの型を使用している場合には使用できません。

コンカレンシー ランタイムは非常に組み合わせ自在で、既存の機能を結合して機能を拡張できます。 コンカレンシー ランタイムでは、下位のコンポーネントから、並列アルゴリズムなど多数の機能を構成します。

また、コンカレンシー ランタイムでは、協調ブロッキングを使用して、リソースへのアクセスを同期する同期プリミティブも提供されます。 これらの同期プリミティブの詳細については、「同期データ構造」を参照してください。

以下のセクションでは、各コンポーネントが備えている機能と使用する場面についての概要を簡単に説明します。

並列パターン ライブラリ

並列パターン ライブラリ (PPL) は、粒度の細かい並列化を実行するための汎用的なコンテナーとアルゴリズムを提供します。 PPL では、コレクションまたはデータのセットに対する計算をコンピューティング リソースに分散する並列アルゴリズムを提供して、命令型データの並列化を実現しています。 複数の個別の演算をコンピューティング リソースに分散するタスク オブジェクトを提供して、タスクの並列化を可能にしています。

ローカルの計算で並列実行の利点を活用できる場合は、並列パターン ライブラリを使用します。 たとえば、concurrency::parallel_for アルゴリズムを使用して、既存の forループを並列で動作するように変換できます。

並列パターン ライブラリの詳細については、「並列パターン ライブラリ (PPL)」を参照してください。

非同期エージェント ライブラリ

非同期エージェント ライブラリ (または単にエージェント ライブラリ) は、アクターベースのプログラミング モデルに加えて、粒度の粗いデータ フローおよびパイプライン処理タスクのメッセージ パッシング インターフェイスの役割も果たします。 非同期エージェントを使用すると、他のコンポーネントがデータを待機しているときに作業を実行することにより、待機時間を生産的に活用できます。

相互に非同期通信を行う複数のエンティティがある場合に、エージェント ライブラリを使用します。 たとえば、データをファイルまたはネットワーク接続から読み取って、そのデータをメッセージ パッシング インターフェイスで別のエージェントに送信するエージェントを作成できます。

エージェント ライブラリの詳細については、「非同期エージェント ライブラリ」を参照してください。

タスク スケジューラ

タスク スケジューラは、実行時にタスクをスケジュールおよび調整します。 タスク スケジューラは他の処理と連携して行われ、ワーク スティーリング アルゴリズムを使用して処理リソースを最大限に活用します。

コンカレンシー ランタイムには既定のスケジューラが用意されているため、インフラストラクチャの詳細を管理する必要はありません。 ただし、アプリケーションの品質ニーズを満たすために、独自のスケジューリング ポリシーを用意したり、特定のスケジューラを特定のタスクに関連付けたりすることもできます。

タスク スケジューラの詳細については、「タスク スケジューラ 」を参照してください。

Resource Manager

リソース マネージャーの役割は、プロセッサやメモリなどのコンピューティング リソースを管理することです。 リソース マネージャーは、実行時の作業負荷の変更に応答して、効果が最も大きくなる場所にリソースを割り当てます。

リソース マネージャーは、コンピューティング リソースの抽象化として機能し、主にタスク スケジューラとやり取りします。 リソース マネージャーを使用してライブラリおよびアプリケーションのパフォーマンスを微調整できますが、通常は、並列パターン ライブラリ、エージェント ライブラリ、およびタスク スケジューラに備わった機能を使用します。 これらのライブラリでは、リソース マネージャーを使用して、作業負荷の変更に応じてリソースのバランスを直接的に再調整します。

[トップ]

C++ ラムダ式

コンカレンシー ランタイムで定義されている型やアルゴリズムの多くは、C++ テンプレートとして実装されています。 こうした型やアルゴリズムの中には、処理を実行するためのルーチンをパラメーターとして受け取るものがあります。 このパラメーターには、ラムダ関数、関数オブジェクト、または関数ポインターを使用できます。 これらのエンティティは、処理関数または処理ルーチンとも呼ばれます。

ラムダ式は、Visual C++ 言語の重要な新機能の 1 つです。ラムダ式を使用すると、並列処理用の処理関数を簡潔に定義できます。 関数オブジェクトおよび関数ポインターを使用すると、既存のコードでコンカレンシー ランタイムを使用できます。 ただし、新しいコードを記述するときには、安全性や生産性の面で優れたラムダ式を使用することをお勧めします。

次の例では、concurrency::parallel_for_each アルゴリズムを複数回呼び出す場合のラムダ関数、関数オブジェクト、および関数ポインターの構文を比較しています。 parallel_for_each を呼び出している各コードは、それぞれ異なる手法を用いて std::array オブジェクト内の各要素の 2 乗を計算しています。

// comparing-work-functions.cpp
// compile with: /EHsc
#include <ppl.h>
#include <array>
#include <iostream>

using namespace concurrency;
using namespace std;

// Function object (functor) class that computes the square of its input.
template<class Ty>
class SquareFunctor
{
public:
   void operator()(Ty& n) const
   {
      n *= n;
   }
};

// Function that computes the square of its input.
template<class Ty>
void square_function(Ty& n)
{
   n *= n;
}

int wmain()
{
   // Create an array object that contains 5 values.
   array<int, 5> values = { 1, 2, 3, 4, 5 };

   // Use a lambda function, a function object, and a function pointer to 
   // compute the square of each element of the array in parallel.

   // Use a lambda function to square each element.
   parallel_for_each(begin(values), end(values), [](int& n){n *= n;});

   // Use a function object (functor) to square each element.
   parallel_for_each(begin(values), end(values), SquareFunctor<int>());

   // Use a function pointer to square each element.
   parallel_for_each(begin(values), end(values), &square_function<int>);

   // Print each element of the array to the console.
   for_each(begin(values), end(values), [](int& n) { 
      wcout << n << endl;
   });
}

出力

1
256
6561
65536
390625

C++ でのラムダ関数の詳細については、「ラムダ式」を参照してください。

[トップ]

必要条件

次の表は、コンカレンシー ランタイムの各コンポーネントに関連付けられているヘッダー ファイルを示しています。

コンポーネント ヘッダー ファイル
並列パターン ライブラリ (PPL) ppl.h

concurrent_queue.h

concurrent_vector.h
非同期エージェント ライブラリ agents.h
タスク スケジューラ concrt.h
Resource Manager concrtrm.h

コンカレンシー ランタイムは、Concurrency 名前空間で宣言されています。 (この名前空間のエイリアスであるコンカレンシーを使用することもできます)。名前空間はconcurrency::detailsコンカレンシー ランタイム フレームワークをサポートしており、コードから直接使用するためのものではありません。

コンカレンシー ランタイムは、C ランタイム ライブラリ (CRT) の一部として提供されます。 CRT を使用するアプリケーションをビルドする方法の詳細については、「CRT ライブラリ機能」を参照してください。

[トップ]