Vue d'ensemble du runtime d'accès concurrentiel

Ce document fournit une vue d'ensemble du runtime d'accès concurrentiel. Il décrit les avantages du runtime d'accès concurrentiel, quand l'utiliser et la façon dont ses composants interagissent entre eux et avec le système d'exploitation et les applications.

Sections

Ce document contient les sections suivantes :

Historique d’implémentation du runtime d’accès concurrentiel

Dans Visual Studio 2010 à 2013, le runtime d’accès concurrentiel a été incorporé dans msvcr100.dll via msvcr120.dll. Lorsque la refactorisation UCRT s’est produite dans Visual Studio 2015, cette DLL a été refactorisé en trois parties :

  • ucrtbase.dll – API C, fournie dans Windows 10 et serviced downlevel via Windows Update -

  • vcruntime140.dll : le compilateur prend en charge les fonctions et le runtime EH, expédiés via Visual Studio

  • concrt140.dll – Runtime d’accès concurrentiel, fourni via Visual Studio. Requis pour les conteneurs et algorithmes parallèles tels que concurrency::parallel_for. En outre, la bibliothèque STL requiert cette DLL sur Windows XP pour alimenter les primitives de synchronisation, car Windows XP n’a pas de variables de condition.

Dans Visual Studio 2015 et versions ultérieures, le planificateur de tâches du runtime d'accès concurrentiel n'est plus le planificateur de la classe de tâche et des types associés dans ppltasks.h. Ces types utilisent désormais le pool de threads Windows pour de meilleures performances et une meilleure interopérabilité avec les primitives de synchronisation Windows.

Pourquoi un runtime pour la concurrence est important

Un runtime d'accès concurrentiel fournit l'uniformité et la prévisibilité aux applications et à leurs composants qui s'exécutent simultanément. Deux exemples des avantages du runtime d’accès concurrentiel sont la planification de tâches coopératives et le blocage coopératif.

Le runtime d'accès concurrentiel utilise un planificateur de tâches coopératif qui implémente un algorithme de vol de travail pour distribuer efficacement le travail entre les ressources informatiques. Par exemple, considérez une application qui a deux threads gérés par le même runtime. Si un thread termine sa tâche planifiée, il peut décharger du travail de l’autre thread. Ce mécanisme équilibre la charge de travail globale de l'application.

Le runtime d’accès concurrentiel fournit également des primitives de synchronisation qui utilisent le blocage coopératif pour synchroniser l’accès aux ressources. Par exemple, considérez une tâche qui doit avoir un accès exclusif à une ressource partagée. Par un blocage coopératif, le runtime peut utiliser le quantum restant pour effectuer une autre tâche, pendant que la première tâche attend la ressource. Ce mécanisme favorise l'utilisation maximale des ressources informatiques.

[Haut]

Architecture

Le runtime d'accès concurrentiel se divise en quatre composants : la bibliothèque de modèles parallèles (PPL), la bibliothèque d'agents asynchrones, le planificateur de tâches et le gestionnaire des ressources. Ces composants résident entre le système d'exploitation et les applications. L'illustration suivante montre comment les composants du runtime d'accès concurrentiel interagit entre le système d'exploitation et les applications :

Architecture du runtime d’accès concurrentiel

The Concurrency Runtime Architecture.

Important

Les composants Task Scheduler et Resource Manager ne sont pas disponibles à partir d’une application plateforme Windows universelle (UWP) ou lorsque vous utilisez la classe de tâches ou d’autres types dans ppltasks.h.

Le runtime d’accès concurrentiel est hautement composable, c’est-à-dire que vous pouvez combiner des fonctionnalités existantes pour en faire plus. Le runtime d’accès concurrentiel compose de nombreuses fonctionnalités, telles que des algorithmes parallèles, à partir de composants de niveau inférieur.

Le runtime d’accès concurrentiel fournit également des primitives de synchronisation qui utilisent le blocage coopératif pour synchroniser l’accès aux ressources. Pour plus d’informations sur ces primitives de synchronisation, consultez Structures de données de synchronisation.

Les sections suivantes fournissent un bref aperçu de ce que fournit chaque composant et quand les utiliser.

bibliothèque de modèles parallèles

La bibliothèque de modèles parallèles (PPL) fournit des algorithmes et des conteneurs à usage général pour effectuer un parallélisme affiné. Le PPL permet un parallélisme impératif des données en fournissant des algorithmes parallèles qui distribuent des calculs sur des collections ou sur des ensembles de données entre les ressources informatiques. Il permet également le parallélisme des tâches en fournissant des objets de tâche qui distribuent plusieurs opérations indépendantes entre les ressources informatiques.

Utilisez la bibliothèque de modèles parallèles quand vous avez un calcul local qui peut tirer parti d’une exécution en parallèle. Par exemple, vous pouvez utiliser l’algorithme concurrency ::p arallel_for pour transformer une boucle existante for pour agir en parallèle.

Pour plus d’informations sur la bibliothèque de modèles parallèles, consultez Bibliothèque de modèles parallèles (PPL).

bibliothèque d’agents asynchrones

La bibliothèque d’agents asynchrones (ou simplement la bibliothèque d’agents) fournit à la fois un modèle de programmation basé sur un acteur et des interfaces de transmission de messages pour les tâches de flux de données grossières et de pipeline. Les agents asynchrones vous permettent d'utiliser de façon productive la latence, en effectuant le travail pendant que d'autres composants attendent des données.

Utilisez la bibliothèque d'agents quand vous avez plusieurs entités qui communiquent entre elles de façon asynchrone. Par exemple, vous pouvez créer un agent qui lit des données à partir d'un fichier ou d'une connexion réseau, puis utilise les interfaces de passage de messages pour envoyer ces données à un autre agent.

Pour plus d’informations sur la bibliothèque d’agents, consultez Bibliothèque d’agents asynchrones.

Planificateur de tâches

Le planificateur de tâches planifie et coordonne les tâches au moment de l'exécution. Il est coopératif et utilise un algorithme de vol de travail pour optimiser l'utilisation des ressources de traitement.

Le runtime d'accès concurrentiel fournit un planificateur par défaut pour que vous n'ayez pas à gérer les détails de l'infrastructure. Toutefois, pour répondre aux besoins de qualité de votre application, vous pouvez également fournir votre propre stratégie de planification ou associer des planificateurs spécifiques à des tâches spécifiques.

Pour plus d’informations sur le planificateur de tâches, consultez Planificateur de tâches.

Gestionnaire de ressources

Le rôle du gestionnaire des ressources est de gérer les ressources informatiques, comme les processeurs et la mémoire. Le gestionnaire des ressources répond aux charges de travail à mesure qu'elles changent au moment de l'exécution en affectant des ressources là où elles peuvent être les plus efficaces.

Le gestionnaire des ressources sert d’abstraction sur les ressources informatiques et interagit principalement avec le planificateur de tâches. Bien que vous puissiez utiliser le gestionnaire des ressources pour ajuster les performances de vos bibliothèques et applications, vous utilisez généralement la fonctionnalité fournie par la bibliothèque de modèles parallèles, la bibliothèque d'agents et le planificateur de tâches. Ces bibliothèques utilisent le gestionnaire des ressources pour rééquilibrer de manière dynamique les ressources à mesure que les charges de travail évoluent.

[Haut]

C++ Lambda Expressions

La plupart des types et algorithmes définis par le runtime d'accès concurrentiel sont implémentés en tant que modèles C++. Certains de ces types et algorithmes prennent comme paramètre une routine qui effectue le travail. Ce paramètre peut être une fonction lambda, un objet de fonction ou un pointeur de fonction. Ces entités sont également appelées fonctions de travail ou routines de travail.

Les expressions lambda sont une nouvelle fonctionnalité importante du langage Visual C++, car elles offrent un moyen concis de définir des fonctions de travail pour un traitement parallèle. Les objets de fonction et les pointeurs de fonction vous permettent d'utiliser le runtime d'accès concurrentiel avec votre code existant. Toutefois, nous vous recommandons d'utiliser des expressions lambda quand vous écrivez un nouveau code en raison des avantages en matière de sécurité et de productivité qu'elles apportent.

L’exemple suivant compare la syntaxe des fonctions lambda, des objets de fonction et des pointeurs de fonction dans plusieurs appels à l’algorithme concurrency ::p arallel_for_each . Chaque appel à parallel_for_each utiliser une technique différente pour calculer le carré de chaque élément dans un objet std ::array .

// 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;
   });
}

Sortie

1
256
6561
65536
390625

Pour plus d’informations sur les fonctions lambda en C++, consultez Expressions lambda.

[Haut]

Spécifications

Le tableau suivant présente les fichiers d'en-tête associés à chaque composant du runtime d'accès concurrentiel :

Composant Fichiers d’en-tête
Bibliothèque de modèles parallèles ppl.h

concurrent_queue.h

concurrent_vector.h
bibliothèque d’agents asynchrones agents.h
Planificateur de tâches concrt.h
Gestionnaire de ressources concrtrm.h

Le runtime d’accès concurrentiel est déclaré dans l’espace de noms concurrency . (Vous pouvez également utiliser l’accès concurrentiel, qui est un alias pour cet espace de noms.) L’espace concurrency::details de noms prend en charge l’infrastructure d’exécution concurrentiel et n’est pas destiné à être utilisé directement à partir de votre code.

Le runtime d'accès concurrentiel est fourni dans le cadre de la bibliothèque Runtime C (CRT). Pour plus d’informations sur la création d’une application qui utilise le CRT, consultez Fonctionnalités de la bibliothèque CRT.

[Haut]