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. Laconcurrent_vector
classe ne fournit pas lainsert
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 oupop_back
leserase
méthodes. Comme avecvector
, utilisez la méthode clear pour supprimer tous les éléments d’unconcurrent_vector
objet.La
concurrent_vector
classe ne stocke pas ses éléments contigus en mémoire. Par conséquent, vous ne pouvez pas utiliser laconcurrent_vector
classe de toutes les façons dont vous pouvez utiliser un tableau. Par exemple, pour une variable nomméev
de typeconcurrent_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
typebool
.
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, reserve
ne 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, T
doit 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 oupop
lesfront
méthodes. Laconcurrent_queue
classe remplace ces méthodes par la définition de la méthode try_pop .La
concurrent_queue
classe ne fournit pas laback
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 lasize
méthode. Launsafe_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
erase
méthodes , ,bucket
bucket_count
etbucket_size
sont nomméesunsafe_erase
,unsafe_bucket
unsafe_bucket_count
, etunsafe_bucket_size
, respectivement. Launsafe_
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 :
La méthode concurrent_unordered_multimap ::insert retourne un itérateur au lieu de
std::pair<iterator, bool>
.La
concurrent_unordered_multimap
classe ne fournitoperator[]
pas ni laat
méthode.
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 :
La méthode concurrent_unordered_multiset ::insert retourne un itérateur au lieu de
std::pair<iterator, bool>
.La
concurrent_unordered_multiset
classe ne fournitoperator[]
pas ni laat
méthode.
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 :
Guide pratique pour utiliser la classe combinable pour améliorer les performances
Guide pratique pour utiliser la classe combinable pour combiner des ensembles
[Haut]
Rubriques connexes
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_unordered_map, classe
concurrent_unordered_multimap, classe
concurrent_unordered_set, classe