Conteneurs et objets parallèles

La Bibliothèque de modèles parallèles (PPL, Parallel Patterns Library) inclut plusieurs conteneurs et objets qui fournissent un accès thread-safe à leurs éléments.

Un conteneur simultané fournit un accès concurrentiel sécurisé aux opérations les plus importantes. Les fonctionnalités de ces conteneurs ressemblent à celles fournies par la Bibliothèque de modèles standard (STL, Standard Template Library). Par exemple, la classe Concurrency::concurrent_vector s'apparente à la classe std::vector, hormis le fait que la classe concurrent_vector vous permet d'ajouter des éléments en parallèle. Utilisez des conteneurs simultanés lorsque vous avez du code parallèle qui requiert un accès en lecture et en écriture au même conteneur.

Un objet simultané est partagé simultanément par des composants. Un processus qui calcule l'état d'un objet simultané en parallèle produit le même résultat qu'un autre processus qui calcule le même état de façon séquentielle. La classe Concurrency::combinable est un exemple de type d'objet simultané. La classe combinable vous permet d'effectuer des calculs en parallèle, puis combine ces calculs dans un résultat final. Utilisez des objets simultanés lorsque vous utiliseriez normalement un mécanisme de synchronisation, par exemple un mutex, pour synchroniser l'accès à une variable ou ressource partagée.

Sections

Cette rubrique décrit en détail les conteneurs et objets parallèles suivants.

Conteneurs simultanés :

  • Classe concurrent_vector

  • Classe concurrent_queue

Objets simultanés :

  • Classe combinable

Classe concurrent_vector

La classe Concurrency::concurrent_vector est une classe de conteneur de séquence qui, tout comme la classe std::vector, vous permet d'accéder aléatoirement à ses éléments. La classe concurrent_vector permet d'effecteur des opérations d'ajout et d'accès aux éléments sécurisées du point de vue de l'accès concurrentiel. Les opérations d'ajout n'invalident pas les pointeurs ou itérateurs existants. Les opérations de parcourt et d'accès aux itérateurs sont également sécurisées du point de vue de l'accès concurrentiel.

Différences entre concurrent_vector et vector

La classe concurrent_vector ressemble étroitement à la classe vector. La complexité des opérations d'ajout, d'accès aux éléments et d'accès aux itérateurs sur un objet concurrent_vector sont les mêmes que pour un objet vector. Les points suivants illustrent dans quelle mesure concurrent_vector diffère de vector :

  • Les opérations d'ajout, d'accès aux éléments, d'accès aux itérateurs et de parcourt des itérateurs sur un objet concurrent_vector sont sécurisées du point de vue de l'accès concurrentiel.

  • Vous pouvez ajouter des éléments uniquement à la fin d'un objet concurrent_vector. La classe concurrent_vector ne fournit pas la méthode insert.

  • Un objet concurrent_vector n'utilise pas la sémantique de déplacement lorsque vous le soumettez à un ajout.

  • La classe concurrent_vector ne fournit pas la méthode erase ou pop_back. Comme dans le cas de vector, utilisez la méthode clear pour supprimer tous les éléments d'un objet concurrent_vector.

  • La classe concurrent_vector ne stocke pas ses éléments en mémoire de façon contiguë. Par conséquent, vous ne pouvez pas utiliser la classe concurrent_vector de toutes les manières que vous pouvez utiliser un tableau. Par exemple, pour une variable nommée v de type concurrent_vector, l'expression &v[0]+2 produit un comportement indéfini.

  • La classe concurrent_vector définit les méthodes grow_by et grow_to_at_least. Ces méthodes ressemblent à la méthode resize, mais elles sont sécurisées du point de vue de l'accès concurrentiel.

  • Un objet concurrent_vector ne déplace pas ses éléments lorsque vous le redimensionnez ou vous le soumettez à un ajout. Cela permet aux pointeurs et aux itérateurs existants de rester valides pendant les opérations simultanées.

  • Le runtime ne définit pas de version spécialisée de concurrent_vector pour le type bool.

Opérations à accès concurrentiel sécurisé

Toutes les méthodes qui ajoutent à ou augmentent la taille d'un objet concurrent_vector, ou accède à un élément dans un objet concurrent_vector, sont sécurisées du point de vue de l'accès concurrentiel. L'exception à cette règle est la méthode resize.

Le tableau suivant répertorie les méthodes et les opérateurs concurrent_vector courants qui sont sécurisées du point de vue de l'accès concurrentiel.

at

end

operator[]

begin

front

push_back

back

grow_by

rbegin

capacity

grow_to_at_least

rend

empty

max_size

taille

Les opérations fournies par le runtime à des fins de compatibilité avec la bibliothèque STL, par exemple reserve, ne sont pas sécurisées du point de vue de l'accès concurrentiel. Le tableau suivant répertorie les méthodes et opérateurs courants qui ne sont pas sécurisées du point de vue de l'accès concurrentiel.

assign

reserve

clear

resize

operator=

shrink_to_fit

Les opérations qui modifient la valeur d'éléments existants ne sont pas sécurisées du point de vue de l'accès concurrentiel. Utilisez un objet de synchronisation, tel qu'un objet reader_writer_lock, pour synchroniser des opérations en lecture et en écriture simultanées au même élément de données. Pour plus d'informations sur les objets de synchronisation, consultez Structures de données de synchronisation.

Lorsque vous convertissez du code existant qui utilise vector de façon à utiliser concurrent_vector, les opérations simultanées peuvent provoquer un changement de comportement de votre application. Prenons l'exemple du programme suivant, qui effectue simultanément deux tâches sur un objet concurrent_vector. La première tâche ajoute des éléments supplémentaires à un objet concurrent_vector. La deuxième tâche calcule la somme de tous les éléments dans le même objet.

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

Bien que la méthode end soit sécurisée du point de vue de l'accès concurrentiel, un appel simultané à la méthode push_back provoque une modification de la valeur retournée par end. Le nombre d'éléments parcourus par l'itérateur est indéterminé. Par conséquent, ce programme peut produire un résultat différent à chaque fois que vous l'exécutez.

Sécurité des exceptions

Si une opération d'assignation ou de croissance lève une exception, l'état de l'objet concurrent_vector devient non valide. Le comportement d'un objet concurrent_vector qui est dans un état non valide est indéfini, sauf indication contraire. Néanmoins, le destructeur libère toujours la mémoire allouée par l'objet, même si l'objet est dans un état non valide.

Le type de données des éléments vectoriels, _Ty, doit satisfaire les spécifications suivantes. Sinon, le comportement de la classe concurrent_vector est indéfini.

  • Le destructeur ne doit pas lever.

  • Si le constructeur de copie ou par défaut lève, le destructeur ne doit pas être déclaré à l'aide du mot clé virtual et il doit fonctionner correctement avec une mémoire initialisée à zéro.

[retour en haut]

Classe concurrent_queue

La classe Concurrency::concurrent_queue, tout comme la classe std::queue, vous permet d'accéder à ses éléments avant et arrière. La classe concurrent_queue permet d'effectuer des opérations de mise en file d'attente et de sortie de file d'attente sécurisées du point de vue de l'accès concurrentiel. La classe concurrent_queue fournit également une prise en charge des itérateurs qui n'est pas sécurisée du point de vue de l'accès concurrentiel.

Différences entre concurrent_queue et queue

La classe concurrent_queue ressemble étroitement à la classe queue. Les points suivants illustrent dans quelle mesure concurrent_queue diffère de queue :

  • Les opérations de mise en file d'attente et de sortie de file d'attente sur un objet concurrent_queue sont sécurisées du point de vue de l'accès concurrentiel.

  • La classe concurrent_queue fournit une prise en charge des itérateurs qui n'est pas sécurisée du point de vue de l'accès concurrentiel.

  • La classe concurrent_queue ne fournit pas la méthode front ou pop. La classe concurrent_queue remplace ces méthodes par la définition de la méthode try_pop.

  • La classe concurrent_queue ne fournit pas la méthode back. Par conséquent, vous ne pouvez pas faire référence à la fin de la file d'attente.

  • La classe concurrent_queue fournit la méthode unsafe_size au lieu de la méthode size. La méthode unsafe_size n'est pas sécurisée du point de vue de l'accès concurrentiel.

Opérations à accès concurrentiel sécurisé

Toutes les méthodes qui mettent en file d'attente ou sortent de file d'attente dans ou à partir d'un objet concurrent_queue sont sécurisées du point de vue de l'accès concurrentiel.

Le tableau suivant répertorie les méthodes et les opérateurs concurrent_queue courants qui sont sécurisées du point de vue de l'accès concurrentiel.

empty

push

get_allocator

try_pop

Bien que la méthode empty soit sécurisée du point de vue de l'accès concurrentiel, une opération simultanée peut provoquer la croissance ou la réduction de la file d'attente avant le retour de la méthode empty.

Le tableau suivant répertorie les méthodes et opérateurs courants qui ne sont pas sécurisées du point de vue de l'accès concurrentiel.

clear

unsafe_end

unsafe_begin

unsafe_size

Prise en charge des itérateurs

concurrent_queue fournit des itérateurs qui ne sont pas sécurisés du point de vue de l'accès concurrentiel. Nous vous recommandons d'utiliser ces itérateurs uniquement à des fins de débogage.

Un itérateur concurrent_queue parcourt des éléments uniquement dans le sens avant. Le tableau suivant répertorie les opérateurs pris en charge par chaque itérateur.

Opérateur

Description

operator++

Avance jusqu'au prochain élément dans la file d'attente. Cet opérateur est surchargé pour fournir une sémantique pré-incrément et post-incrément.

operator*

Extrait une référence à l'élément actuel.

operator->

Extrait un pointeur vers l'élément actuel.

[retour en haut]

Classe combinable

La classe Concurrency::combinable fournit un stockage local des threads réutilisable qui vous permet d'effectuer des calculs affinés, puis de fusionner ces calculs dans un résultat final. On peut considérer un objet combinable comme une variable de réduction.

La classe combinable est utile lorsque vous avez une ressource partagée par plusieurs threads ou tâches. La classe combinable vous aide à éliminer l'état partagé en fournissant l'accès aux ressources partagées sans verrou. Par conséquent, cette classe procure une alternative à l'utilisation d'un mécanisme de synchronisation, par exemple un mutex, pour synchroniser l'accès aux données partagées à partir de plusieurs threads.

Méthodes et fonctionnalités

Le tableau suivant montre quelques-unes des méthodes importantes de la classe combinable. Pour plus d'informations sur toutes les méthodes de la classe combinable, consultez combinable, classe.

Méthode

Description

locales

Extrait une référence à la variable locale associée au contexte de thread actuel.

clear

Supprime toutes les variables de thread local de l'objet combinable.

combine

combine_each

Utilise la fonction combine fournie pour générer une valeur finale à partir de l'ensemble de tous les calculs de thread locaux.

La classe combinable est une classe de modèle paramétrable sur le résultat fusionné final. Si vous appelez le constructeur par défaut, le type de paramètre de modèle _Ty doit avoir un constructeur par défaut et un constructeur de copie. Si le type de paramètre de modèle _Ty n'a pas de constructeur par défaut, appelez la version surchargée du constructeur qui prend une fonction d'initialisation comme paramètre.

Vous pouvez stocker des données supplémentaires dans un objet combinable après avoir appelé la méthode combine ou combine_each. Vous pouvez également appeler les méthodes combine et combine_each à plusieurs reprises. Si aucune valeur locale dans un objet combinable ne change, les méthodes combine_each et combine produisent le même résultat chaque fois qu'elles sont appelées.

Exemples

Pour obtenir des exemples d'utilisation de la classe combinable, consultez les rubriques suivantes :

[retour en haut]

Rubriques connexes

Référence

Classe concurrent_vector

concurrent_queue, classe

combinable, classe