Partager via


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

    • Différences entre concurrent_vector et vector

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

    • Sécurité des exceptions

  • Classe concurrent_queue

    • Différences entre concurrent_queue et queue

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

    • Prise en charge des itérateurs

  • concurrent_unordered_map classe

    • Différences entre le concurrent_unordered_map et l'unordered_map

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

  • concurrent_unordered_multimap classe

  • concurrent_unordered_set classe

  • concurrent_unordered_multiset classe

Objets simultanés :

  • Classe combinable

    • Méthodes et fonctionnalités

    • Exemples

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

[Premières]

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.

opérateur->

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

[Premières]

concurrent_unordered_map classe

La classe concurrency::concurrent_unordered_map est une classe de conteneur associative qui, comme la classe std::unordered_map contrôle une séquence de longueur variable d'éléments de type std::pair<clé const, Ty>. Pensez à une table non-ordonnée comme dictionnaire à laquelle vous pouvez ajouter une paire de clé et valeur ou rechercher une valeur par clé. Cette classe est utile lorsque vous disposez de plusieurs threads ou tâches qui doivent accéder simultanément à un conteneur partagé, y insérer des éléments, ou la mettre à jour.

L'exemple suivant illustre la structure de base pour l'utilisation de concurrent_unordered_map. Cet exemple insère des clés de caractères dans la plage [« a », « i »]. Étant donné que l'ordre des opérations est indéterminé, la valeur finale pour chaque clé est également indéterminée. Toutefois, il est sécurisé pour exécuter 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 un exemple qui utilise concurrent_unordered_map pour effectuer un mappage et réduire l'opération en parallèle, consultez Comment : exécuter des opérations de mappage et de réduction en parallèle.

Différences entre le concurrent_unordered_map et l'unordered_map

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

  • Les méthodes erase, bucket, bucket_count, et bucket_size sont appelées respectivement unsafe_erase, unsafe_bucket, unsafe_bucket_count, et unsafe_bucket_size. La convention d'affectation des noms de unsafe_ indique que ces méthodes ne sont pas sécurisées pour l'accès concurrentiel. Pour plus d'informations sur la sécurité d'accès concurrentiel, consultez Opérations de sécurité d'accès concurrentiel.

  • Les opérations d'insertion n'invalident pas les pointeurs existants ou les itérateurs, ni ne modifient l'ordre des éléments qui existent déjà dans le mappage. Les opérations d'insertion et de parcours peuvent se produire simultanément.

  • concurrent_unordered_map s'appuie uniquement sur l'itération de transfert.

  • L'insertion n'entraîne pas et 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. Les itérateurs de départ pointent sur un élément égal.

Pour éviter le blocage, aucune méthode de concurrent_unordered_map ne contient un verrou quand elle appelle l'allocateur de mémoire, les fonctions de hachage, ou un autre code défini par l'utilisateur. En outre, vous devez vous assurer que la fonction de hachage évalue toujours les clés à valeur égales. Les meilleures fonctions de hachage distribuent des clés uniformément à travers l'espace du code de hachage.

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

La classe concurrent_unordered_map permet d'effectuer 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'insertion 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. Le tableau suivant répertorie les méthodes et les opérateurs concurrent_unordered_map courants qui sont sécurisés du point de vue de l'accès concurrentiel.

at

count

find

key_eq

begin

empty

get_allocator

max_size

cbegin

end

hash_function

operator[]

cend

equal_range

insérer

size

Bien que la méthode count puisse être appelée sans risque depuis des threads simultanés en cours d'éxécution, les différents threads peuvent recevoir des résultats différents si une nouvelle valeur est simultanément insérée dans le conteneur.

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

clear

max_load_factor

rehash

load_factor

operator=

échange

En plus de ces méthodes, aucune méthode qui commence par unsafe_ n'est également sécurisée du point de vue de l'accès concurrentiel.

[Premières]

concurrent_unordered_multimap classe

La classe concurrency::concurrent_unordered_multimap ressemble à la classe concurrent_unordered_map sauf qu'elle autorise plusieurs valeurs à mapper à la même clé. Cela diffère également deconcurrent_unordered_map des façons suivantes :

  • La méthode concurrent_unordered_multimap::insert retourne un itérateur au lieu de std::pair<iterator, bool>.

  • La classe concurrent_unordered_multimap ne fournit pas la méthode operator[] ni la méthode at.

L'exemple suivant illustre la structure de base pour l'utilisation de concurrent_unordered_multimap. Cet exemple insère des clés 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]
*/

[Premières]

concurrent_unordered_set classe

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

L'exemple suivant illustre la structure de base pour l'utilisation de concurrent_unordered_set. Cet exemple insère des valeurs de caractères dans la plage [« a », « i »]. Il est sécurisé pour exécuter 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]
*/

[Premières]

concurrent_unordered_multiset classe

La classe concurrency::concurrent_unordered_multiset ressemble à la classe concurrent_unordered_set sauf qu'elle autorise les valeurs dupliquées. Cela diffère également deconcurrent_unordered_set des façons suivantes :

  • La méthode concurrent_unordered_multiset::insert retourne un itérateur au lieu de std::pair<iterator, bool>.

  • La classe concurrent_unordered_multiset ne fournit pas la méthode operator[] ni la méthode at.

L'exemple suivant illustre la structure de base pour l'utilisation de concurrent_unordered_multiset. Cet exemple insère des valeurs de caractères dans la plage [« a », « i »]. concurrent_unordered_multiset permet à une valeur d'apparaître 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]
*/

[Premières]

Classe combinable

La classe concurrency::combinable fournit un stockage local des threads réutilisables 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 :

[Premières]

Rubriques connexes

Référence

Classe concurrent_vector

concurrent_queue, classe

concurrent_unordered_map, classe

concurrent_unordered_multimap, classe

concurrent_unordered_set, classe

concurrent_unordered_multiset, classe

combinable, classe