Partager via


Conteneurs et objets parallèles

La bibliothèque de modèles parallèles (PPL) comprend 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. Ici, la concurrence-safe signifie que les pointeurs ou les itérateurs sont toujours valides. Il ne s’agit pas d’une garantie d’initialisation d’élément ou d’un ordre de traversée particulier. Les fonctionnalités de ces conteneurs ressemblent à celles fournies par la bibliothèque C++ Standard. Par exemple, la classe concurrency ::concurrent_vector ressemble à la classe std ::vector , sauf que la concurrent_vector classe vous permet d’ajouter des éléments en parallèle. Utilisez des conteneurs simultanés lorsque vous avez du code parallèle qui nécessite à la fois un accès en lecture et en écriture au même conteneur.

Un objet simultané est partagé simultanément entre les 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 en série. La classe concurrency ::combinable est un exemple de type d’objet simultané. La combinable classe vous permet d’effectuer des calculs en parallèle, puis de combiner ces calculs en résultat final. Utilisez des objets simultanés lorsque vous utilisez sinon un mécanisme de synchronisation, par exemple, un mutex, pour synchroniser l’accès à une variable ou une ressource partagée.

Sections

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

Conteneurs simultanés :

Objets simultanés :

Classe concurrent_vector

La classe concurrency ::concurrent_vector est une classe de conteneur de séquences qui, comme la classe std ::vector , vous permet d’accéder de manière aléatoire à ses éléments. La concurrent_vector classe active les opérations d’ajout et d’accès aux éléments concurrentiels sécurisés. Les opérations d’ajout n’invalident pas les pointeurs ou itérateurs existants. L’accès à l’itérateur et les opérations de traversée sont également sécurisés par concurrence. Ici, la concurrence-safe signifie que les pointeurs ou les itérateurs sont toujours valides. Il ne s’agit pas d’une garantie d’initialisation d’élément ou d’un ordre de traversée particulier.

Différences entre concurrent_vector et vecteur

La concurrent_vector classe ressemble étroitement à la vector classe. La complexité des opérations d’ajout, d’accès aux éléments et d’itérateur sur un concurrent_vector objet est identique à celle d’un vector objet. Les points suivants illustrent les concurrent_vector différences entre vector:

  • Les opérations d’ajout, d’accès aux éléments, d’itérateur et d’itérateur sur un concurrent_vector objet sont sécurisées par la concurrence.

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

  • Un concurrent_vector objet n’utilise pas la sémantique de déplacement lorsque vous l’ajoutez.

  • La concurrent_vector classe ne fournit pas les méthodes ou pop_back les erase méthodes. Comme avec vector, utilisez la méthode clear pour supprimer tous les éléments d’un concurrent_vector objet.

  • La concurrent_vector classe ne stocke pas ses éléments contigus en mémoire. Par conséquent, vous ne pouvez pas utiliser la concurrent_vector classe de toutes les façons dont 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 non défini.

  • La concurrent_vector classe définit les méthodes grow_by et grow_to_at_least . Ces méthodes ressemblent à la méthode de redimensionnement , sauf qu’elles sont sécurisées par concurrence.

  • Un concurrent_vector objet ne déplace pas ses éléments lorsque vous l’ajoutez ou le redimensionnez. Cela permet aux pointeurs et 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 type bool.

Opérations de concurrence Coffre

Toutes les méthodes qui ajoutent ou augmentent la taille d’un concurrent_vector objet, ou accèdent à un élément dans un concurrent_vector objet, sont sécurisées par concurrence. Ici, la concurrence-safe signifie que les pointeurs ou les itérateurs sont toujours valides. Il ne s’agit pas d’une garantie d’initialisation d’élément ou d’un ordre de traversée particulier. L’exception à cette règle est la resize méthode.

Le tableau suivant présente les méthodes et opérateurs courants concurrent_vector qui sont sécurisés par concurrence.

Les opérations que le runtime fournit pour la compatibilité avec la bibliothèque standard C++, par exemple, reservene sont pas sécurisées par la concurrence. Le tableau suivant présente les méthodes et opérateurs courants qui ne sont pas sécurisés par concurrence.

Les opérations qui modifient la valeur des éléments existants ne sont pas sécurisées par concurrence. Utilisez un objet de synchronisation tel qu’un objet reader_writer_lock pour synchroniser les opérations de lecture et d’écriture simultanées dans le 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 utilisé vector pour l’utilisation concurrent_vector, les opérations simultanées peuvent entraîner la modification du comportement de votre application. Par exemple, considérez le programme suivant qui effectue simultanément deux tâches sur un concurrent_vector objet. La première tâche ajoute des éléments supplémentaires à un concurrent_vector objet. La deuxième tâche calcule la somme de tous les éléments du 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 = begin(v); i != end(v); ++i) 
         {
            sums.local() += *i;
         }     
         wcout << L"sum = " << sums.combine(plus<int>()) << endl;
      }
   );
}

Bien que la end méthode soit concurrency-safe, un appel simultané à la méthode push_back provoque la modification de la valeur retournée.end Le nombre d’éléments que traverse 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. Lorsque le type d’élément n’est pas trivial, il est possible qu’une condition de concurrence existe entre push_back et end les appels. La end méthode peut retourner un élément alloué, mais pas entièrement initialisé.

Exception Coffre ty

Si une opération de croissance ou d’affectation lève une exception, l’état de l’objet concurrent_vector devient non valide. Le comportement d’un concurrent_vector objet dans un état non valide n’est pas défini, sauf indication contraire. Toutefois, le destructeur libère toujours la mémoire que l’objet alloue, même si l’objet est dans un état non valide.

Le type de données des éléments vectoriels, Tdoit répondre aux exigences suivantes. Sinon, le comportement de la concurrent_vector classe n’est pas défini.

  • Le destructeur ne doit pas lever.

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

[Haut]

concurrent_queue, classe

La classe concurrency ::concurrent_queue , tout comme la classe std ::queue , vous permet d’accéder à ses éléments front et back. La concurrent_queue classe active les opérations de mise en file d’attente et de file d’attente concurrency-safe. Ici, la concurrence-safe signifie que les pointeurs ou les itérateurs sont toujours valides. Il ne s’agit pas d’une garantie d’initialisation d’élément ou d’un ordre de traversée particulier. La concurrent_queue classe fournit également la prise en charge de l’itérateur qui n’est pas sécurisée par la concurrence.

Différences entre concurrent_queue et file d’attente

La concurrent_queue classe ressemble étroitement à la queue classe. Les points suivants illustrent les concurrent_queue différences entre queue:

  • Les opérations de file d’attente et de file d’attente sur un concurrent_queue objet sont sécurisées par la concurrence.

  • La concurrent_queue classe fournit la prise en charge de l’itérateur qui n’est pas sécurisée par la concurrence.

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

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

  • La concurrent_queue classe fournit la méthode unsafe_size au lieu de la size méthode. La unsafe_size méthode n’est pas sécurisée par concurrence.

Opérations de concurrence Coffre

Toutes les méthodes qui en file d’attente vers ou de file d’attente à partir d’un concurrent_queue objet sont concurrency-safe. Ici, la concurrence-safe signifie que les pointeurs ou les itérateurs sont toujours valides. Il ne s’agit pas d’une garantie d’initialisation d’élément ou d’un ordre de traversée particulier.

Le tableau suivant présente les méthodes et opérateurs courants concurrent_queue qui sont sécurisés par concurrence.

Bien que la empty méthode soit concurrency-safe, une opération simultanée peut entraîner la croissance ou la réduction de la file d’attente avant que la empty méthode ne retourne.

Le tableau suivant présente les méthodes et opérateurs courants qui ne sont pas sécurisés par concurrence.

Prise en charge de l’itérateur

Les concurrent_queue itérateurs fournissent des itérateurs qui ne sont pas sécurisés par concurrence. Nous vous recommandons d’utiliser ces itérateurs uniquement pour le débogage.

Un concurrent_queue itérateur traverse les éléments dans la direction vers l’avant uniquement. Le tableau suivant montre les opérateurs pris en charge par chaque itérateur.

Opérateur Description
operator++ Avance jusqu’à l’élément suivant dans la file d’attente. Cet opérateur est surchargé pour fournir à la fois la sémantique de pré-incrémentation et de post-incrément.
operator* Récupère une référence à l’élément actif.
operator-> Récupère un pointeur vers l’élément actif.

[Haut]

concurrent_unordered_map, classe

La classe concurrency ::concurrent_unordered_map est une classe de conteneur associatif qui, comme la classe std ::unordered_map , contrôle une séquence variable d’éléments de type std ::p air<const Key, Ty>. Considérez une carte non ordonnée en tant que dictionnaire que vous pouvez ajouter une paire clé et valeur à ou rechercher une valeur par clé. Cette classe est utile lorsque vous avez plusieurs threads ou tâches qui doivent accéder simultanément à un conteneur partagé, l’insérer dans celui-ci ou le mettre à jour.

L’exemple suivant montre la structure de base à utiliser concurrent_unordered_map. Cet exemple montre comment insérer des touches de caractères dans la plage ['a', 'i']. Étant donné que l’ordre des opérations n’est pas déterminé, la valeur finale de chaque clé est également indéterminée. Toutefois, il est sûr d’effectuer les insertions en parallèle.

// 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]
*/

Pour obtenir un exemple qui utilise concurrent_unordered_map pour effectuer une opération de mappage et de réduction en parallèle, consultez Guide pratique pour effectuer des opérations de mappage et de réduction en parallèle.

Différences entre concurrent_unordered_map et unordered_map

La concurrent_unordered_map classe ressemble étroitement à la unordered_map classe. Les points suivants illustrent les concurrent_unordered_map différences entre unordered_map:

  • Les eraseméthodes , , bucketbucket_countet bucket_size sont nommées unsafe_erase, unsafe_bucketunsafe_bucket_count, et unsafe_bucket_size, respectivement. La unsafe_ convention d’affectation de noms indique que ces méthodes ne sont pas sécurisées par concurrence. Pour plus d’informations sur la sécurité de la concurrence, consultez Opérations d’accès concurrentiel Coffre.

  • Les opérations d’insertion n’invalident pas les pointeurs ou itérateurs existants, ni modifient l’ordre des éléments qui existent déjà dans la carte. Les opérations d’insertion et de traversée peuvent se produire simultanément.

  • concurrent_unordered_map prend uniquement en charge l’itération avant.

  • L’insertion n’invalide pas ou ne met pas à jour les itérateurs retournés par equal_range. L’insertion peut ajouter des éléments inégaux à la fin de la plage. L’itérateur de début pointe vers un élément égal.

Pour éviter tout blocage, aucune méthode de conservation d’un concurrent_unordered_map verrou lorsqu’il appelle l’allocateur de mémoire, les fonctions de hachage ou tout autre code défini par l’utilisateur. En outre, vous devez vous assurer que la fonction de hachage évalue toujours les clés égales à la même valeur. Les meilleures fonctions de hachage distribuent uniformément les clés dans l’espace de code de hachage.

Opérations de concurrence Coffre

La concurrent_unordered_map classe active les opérations d’insertion sécurisée et d’accès aux éléments de concurrence. Les opérations d’insertion n’invalident pas les pointeurs ou itérateurs existants. L’accès à l’itérateur et les opérations de traversée sont également sécurisés par concurrence. Ici, la concurrence-safe signifie que les pointeurs ou les itérateurs sont toujours valides. Il ne s’agit pas d’une garantie d’initialisation d’élément ou d’un ordre de traversée particulier. Le tableau suivant présente les méthodes et opérateurs couramment utilisés concurrent_unordered_map qui sont sécurisés par concurrence.

Bien que la count méthode puisse être appelée en toute sécurité à partir de threads en cours d’exécution simultanée, différents threads peuvent recevoir des résultats différents si une nouvelle valeur est insérée simultanément dans le conteneur.

Le tableau suivant présente les méthodes et opérateurs couramment utilisés qui ne sont pas sécurisés par concurrence.

En plus de ces méthodes, toute méthode commençant par unsafe_ n’est pas non plus sécurisée par la concurrence.

[Haut]

concurrent_unordered_multimap, classe

La classe concurrency ::concurrent_unordered_multimap ressemble étroitement à la concurrent_unordered_map classe, sauf qu’elle permet à plusieurs valeurs de mapper à la même clé. Il diffère également des concurrent_unordered_map méthodes suivantes :

L’exemple suivant montre la structure de base à utiliser concurrent_unordered_multimap. Cet exemple montre comment insérer des touches de caractères dans la plage ['a', 'i']. concurrent_unordered_multimap permet à une clé d’avoir plusieurs valeurs.

// 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]
*/

[Haut]

concurrent_unordered_set, classe

La classe concurrency ::concurrent_unordered_set ressemble étroitement à la concurrent_unordered_map classe, sauf qu’elle gère les valeurs au lieu de paires clé et valeur. La concurrent_unordered_set classe ne fournit operator[] pas ni la at méthode.

L’exemple suivant montre la structure de base à utiliser concurrent_unordered_set. Cet exemple insère des valeurs de caractères dans la plage ['a', 'i']. Il est sûr d’effectuer les insertions en parallèle.

// 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]
*/

[Haut]

concurrent_unordered_multiset, classe

La classe concurrency ::concurrent_unordered_multiset ressemble étroitement à la concurrent_unordered_set classe, sauf qu’elle autorise les valeurs dupliquées. Il diffère également des concurrent_unordered_set méthodes suivantes :

L’exemple suivant montre la structure de base à utiliser concurrent_unordered_multiset. Cet exemple insère des valeurs de caractères dans la plage ['a', 'i']. concurrent_unordered_multiset permet à une valeur de se produire plusieurs fois.

// 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]
*/

[Haut]

combinable, classe

La classe concurrency ::combinable fournit un stockage réutilisable et local thread-local qui vous permet d’effectuer des calculs affinés, puis de fusionner ces calculs dans un résultat final. Vous pouvez considérer un objet combinable comme une variable de réduction.

La combinable classe est utile lorsque vous avez une ressource partagée entre plusieurs threads ou tâches. La combinable classe vous aide à éliminer l’état partagé en fournissant l’accès aux ressources partagées de manière sans verrou. Par conséquent, cette classe offre 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 présente certaines des méthodes importantes de la combinable classe. Pour plus d’informations sur toutes les méthodes de combinable classe, consultez la classe combinable.

Méthode Description
local Récupère une référence à la variable locale associée au contexte de thread actuel.
clear Supprime toutes les variables locales de thread de l’objet combinable .
combine

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

La combinable classe est une classe de modèle paramétrée sur le résultat fusionné final. Si vous appelez le constructeur par défaut, le T type de paramètre de modèle doit avoir un constructeur par défaut et un constructeur de copie. Si le T type de paramètre de modèle 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 combinable objet après avoir appelé les méthodes de combinaison ou de combine_each. Vous pouvez également appeler plusieurs fois les méthodes et combine_each les combine méthodes. Si aucune valeur locale n’est modifiée dans un combinable objet, les combine méthodes et combine_each produisent le même résultat chaque fois qu’ils sont appelés.

Exemples

Pour obtenir des exemples sur l’utilisation de la combinable classe, consultez les rubriques suivantes :

[Haut]

Guide pratique pour utiliser des conteneurs parallèles pour une efficacité accrue
Montre comment utiliser des conteneurs parallèles pour stocker et accéder efficacement aux données en parallèle.

Guide pratique pour utiliser la classe combinable pour améliorer les performances
Montre comment utiliser la classe pour éliminer l’état combinable partagé et améliorer ainsi les performances.

Guide pratique pour utiliser la classe combinable pour combiner des ensembles
Montre comment utiliser une combine fonction pour fusionner des jeux de données locaux de threads.

Bibliothèque de modèles parallèles
Décrit le PPL, qui fournit un modèle de programmation impératif qui favorise l’extensibilité et la facilité d’utilisation pour le développement d’applications simultanées.

Référence

concurrent_vector, classe

concurrent_queue, classe

concurrent_unordered_map, classe

concurrent_unordered_multimap, classe

concurrent_unordered_set, classe

concurrent_unordered_multiset, classe

combinable, classe