Contenitori e oggetti paralleli
La libreria PPL (Parallel Patterns Library) include diversi contenitori e oggetti che forniscono l'accesso thread-safe ai relativi elementi.
Un contenitore simultaneo fornisce l'accesso indipendente dalla concorrenza alle principali operazioni.La funzionalità di questi contenitori è simile a quella fornita dalla libreria STL (Standard Template Library.Ad esempio, il concurrency::concurrent_vector classe è simile al std:: Vector di classe, con la differenza che il concurrent_vector classe consente di aggiungere elementi in parallelo.Utilizzare i contenitori simultanei quando si dispone del codice parallelo che richiede l'accesso sia in lettura che in scrittura allo stesso contenitore.
Un oggetto simultaneo viene condiviso contemporaneamente tra i componenti.Un processo che calcola lo stato di un oggetto simultaneo in parallelo produce lo stesso risultato di un altro processo che calcola lo stesso stato in serie.Il concurrency::combinable classe è un esempio di un tipo di oggetto concorrenti.La classe combinable consente di eseguire calcoli in parallelo, quindi di combinare tali calcoli in un risultato finale.Utilizzare gli oggetti simultanei quando altrimenti si utilizzerebbe un meccanismo di sincronizzazione, ad esempio un mutex, per sincronizzare l'accesso a una variabile o risorsa condivisa.
Sezioni
In questo argomento vengono descritti in dettaglio gli oggetti e i contenitori paralleli seguenti.
Contenitori simultanei:
Classe concurrent_vector
Differenze tra concurrent_vector e vector
Operazioni indipendenti dalla concorrenza
Sicurezza dell'eccezione
Classe concurrent_queue
Differenze tra concurrent_queue e queue
Operazioni indipendenti dalla concorrenza
Supporto degli iteratori
Classe concurrent_unordered_map
Unordered_map e concurrent_unordered_map le differenze tra
Operazioni indipendenti dalla concorrenza
Classe concurrent_unordered_multimap
Classe concurrent_unordered_set
Classe concurrent_unordered_multiset
Oggetti simultanei:
Classe combinable
Metodi e funzionalità
Esempi
Classe concurrent_vector
Il concurrency::concurrent_vector è una classe di contenitore sequenza che, come il std:: Vector di classe, è possibile accedere in modo casuale gli elementi.La classe concurrent_vector consente le operazioni di accodamento e di accesso elementi in modo indipendente dalla concorrenza.Le operazioni di accodamento non invalidano i puntatori o gli iteratori esistenti.Anche le operazioni di attraversamento e di accesso iteratori sono indipendenti dalla concorrenza.
Differenze tra concurrent_vector e vector
La classe concurrent_vector è molto simile alla classe vector.La complessità delle operazioni di accodamento, accesso elementi e accesso iteratori su un oggetto concurrent_vector è la stessa di quella per un oggetto vector.Di seguito vengono illustrate le differenze tra concurrent_vector e vector:
Le operazioni di accodamento, accesso elementi, accesso iteratori e attraversamento iteratori in un oggetto concurrent_vector sono indipendenti dalla concorrenza.
È possibile aggiungere elementi solo alla fine di un oggetto concurrent_vector.La classe concurrent_vector non fornisce il metodo insert.
Un oggetto concurrent_vector non utilizza la semantica di spostamento quando vengono accodati dati.
La classe concurrent_vector non fornisce i metodi erase o pop_back.Analogamente a vector, utilizzare il metodo clear per rimuove tutti gli elementi da un oggetto concurrent_vector.
La classe concurrent_vector non archivia i relativi elementi in modo contiguo nella memoria.Pertanto, non è possibile utilizzare la classe concurrent_vector in tutti i modi in cui è possibile utilizzare una matrice.Ad esempio, per una variabile denominata v di tipo concurrent_vector, l'espressione &v[0]+2 produce un comportamento indefinito.
La classe concurrent_vector definisce i metodi grow_by e grow_to_at_least.Questi metodi sono simili al metodo resize, ad eccezione del fatto che i primi sono indipendenti dalla concorrenza.
Un oggetto concurrent_vector non riloca i relativi elementi quando vengono accodati dati e viene ridimensionato.In questo modo, i puntatori e gli iteratori esistenti rimangono validi durante le operazioni simultanee.
Il runtime non definisce una versione specializzata di concurrent_vector per il tipo bool.
Operazioni indipendenti dalla concorrenza
Tutti i metodi che accodano dati a un oggetto concurrent_vector o ne aumentano le dimensioni oppure accedono a un elemento in un oggetto concurrent_vector sono indipendenti dalla concorrenza.L'eccezione a questa regola è rappresentata dal metodo resize.
Nella tabella seguente vengono riportati gli operatori e i metodi concurrent_vector comuni indipendenti dalla concorrenza.
Le operazioni che il runtime fornisce ad esempio, per la compatibilità con STL, reserve, non sono validi per la concorrenza.Nella tabella seguente vengono riportati gli operatori e i metodi comuni non indipendenti dalla concorrenza.
Le operazioni che modificano il valore degli elementi esistenti non sono indipendenti dalla concorrenza.Utilizzare un oggetto di sincronizzazione, ad esempio un oggetto reader_writer_lock, per sincronizzare le operazioni simultanee di lettura e scrittura nello stesso elemento dati.Per ulteriori informazioni sugli oggetti di sincronizzazione, vedere Strutture di dati di sincronizzazione.
Quando si converte il codice esistente che utilizza vector per utilizzare concurrent_vector, le operazioni simultanee possono determinare una modifica nel comportamento dell'applicazione.Si consideri ad esempio il seguente programma che esegue contemporaneamente due attività su un oggetto concurrent_vector.La prima attività accoda elementi aggiuntivi a un oggetto concurrent_vector.La seconda attività calcola la somma di tutti gli elementi nello stesso oggetto.
// 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;
}
);
}
Sebbene il metodo end sia indipendente dalla concorrenza, una chiamata simultanea al metodo push_back determina una modifica del valore restituito da end.Il numero di elementi attraversati dall'iteratore è indeterminato.Il programma può pertanto fornire un risultato diverso ogni volta che viene eseguito.
Sicurezza dell'eccezione
Se un'operazione di crescita o di assegnazione genera un'eccezione, lo stato dell'oggetto concurrent_vector diventa non valido.Il comportamento di un oggetto concurrent_vector che si trova in uno stato non valido è indefinito, se non diversamente specificato.Tuttavia, il distruttore libera sempre la memoria allocata dall'oggetto, anche se l'oggetto si trova in uno stato non valido.
Il tipo di dati degli elementi di vettore, _Ty, deve soddisfare i requisiti seguenti.In caso contrario, il comportamento della classe concurrent_vector è indefinito.
Il distruttore non deve essere generato.
Se il costruttore predefinito o di copia viene generato, il distruttore non deve essere dichiarato tramite la parola chiave virtual e deve funzionare correttamente con la memoria inizializzata su zero.
Top
Classe concurrent_queue
Il concurrency::concurrent_queue di classe, come il std::queue di classe, consente di accedere ai relativi anteriore e nuovamente gli elementi.La classe concurrent_queue consente le operazioni di accodamento e di rimozione dalla coda indipendenti dalla concorrenza.La classe concurrent_queue fornisce inoltre il supporto iteratori non indipendente dalla concorrenza.
Differenze tra concurrent_queue e queue
La classe concurrent_queue è molto simile alla classe queue.Di seguito vengono illustrate le differenze tra concurrent_queue e queue:
Le operazioni di accodamento e rimozione dalla coda in un oggetto concurrent_queue sono indipendenti dalla concorrenza.
La classe concurrent_queue fornisce il supporto iteratori non indipendente dalla concorrenza.
La classe concurrent_queue non fornisce i metodi front o pop.La classe concurrent_queue sostituisce questi metodi definendo il metodo try_pop.
La classe concurrent_queue non fornisce il metodo back.Pertanto, non è possibile fare riferimento alla fine della coda.
La classe concurrent_queue fornisce il metodo unsafe_size anziché il metodo size.Il metodo unsafe_size non è indipendente dalla concorrenza.
Operazioni indipendenti dalla concorrenza
Tutti i metodi che accodano dati a un oggetto concurrent_queue o rimuovono i dati dalla coda sono indipendenti dalla concorrenza.
Nella tabella seguente vengono riportati gli operatori e i metodi concurrent_queue comuni indipendenti dalla concorrenza.
Sebbene il metodo empty sia indipendente dalla concorrenza, un'operazione simultanea può determinare un aumento o una riduzione della coda prima della restituzione del metodo empty.
Nella tabella seguente vengono riportati gli operatori e i metodi comuni non indipendenti dalla concorrenza.
Supporto degli iteratori
concurrent_queue fornisce gli iteratori non indipendenti dalla concorrenza.È consigliabile utilizzare questi iteratori solo per l'esecuzione del debug.
Un iteratore concurrent_queue attraversa gli elementi solo in avanti.Nella tabella seguente sono indicati gli operatori supportati da ogni iteratore.
Operatore |
Descrizione |
---|---|
Si posta all'elemento successivo nella coda.Viene eseguito l'overload di questo operatore per fornire la semantica pre-incremento e post-incremento. |
|
Recupera un riferimento all'elemento corrente. |
|
Recupera un puntatore all'elemento corrente. |
Top
Classe concurrent_unordered_map
Il concurrency::concurrent_unordered_map è una classe contenitore associativa che, come il std::unordered_map classi, controlli di una sequenza di lunghezza variabile di elementi di tipo std::pair < chiave const, Ty >.Una mappa non ordinata può essere paragonato a un dizionario che è possibile aggiungere una coppia chiave / valore per o cercare un valore dalla chiave.Questa classe è utile quando si dispone di più thread o attività che devono accedere a un contenitore condiviso, inserire in esso o aggiornarlo contemporaneamente.
Nell'esempio riportato di seguito viene illustrata la struttura di base per l'utilizzo di concurrent_unordered_map.In questo esempio consente di inserire caratteri di tastiera nell'intervallo ['a', ' i'].Poiché l'ordine delle operazioni è determinato, il valore finale per ciascuna chiave anche è indeterminato.Tuttavia, è sicuro eseguire le operazioni di inserimento in parallelo.
// 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]
*/
Per un esempio di utilizzo concurrent_unordered_map per eseguire una mappa e ridurre il funzionamento in parallelo, vedere Procedura: eseguire operazioni di mapping e riduzione in parallelo.
Unordered_map e concurrent_unordered_map le differenze tra
La classe concurrent_unordered_map è molto simile alla classe unordered_map.Di seguito vengono illustrate le differenze tra concurrent_unordered_map e unordered_map:
The erase, bucket, bucket_count, and bucket_size methods are named unsafe_erase, unsafe_bucket, unsafe_bucket_count, and unsafe_bucket_size, respectively.Il unsafe_ convenzione di denominazione indica che questi metodi non sono sicuri della concorrenza.Per ulteriori informazioni sulla protezione della concorrenza, vedere Operazioni indipendenti dalla concorrenza.
Le operazioni di inserimento non invalidare i puntatori esistenti o gli iteratori, né si cambiano l'ordine degli elementi già presenti nella mappa.Inserire e attraversare possono avere luogo operazioni contemporaneamente.
concurrent_unordered_mapsupporta solo l'iterazione in avanti.
Inserimento non invalida o aggiornare gli iteratori che vengono restituiti da equal_range.Inserimento è possibile aggiungere elementi uguali alla fine dell'intervallo.L'iteratore begin punta a un elemento uguale.
Per evitare deadlock, nessun metodo di concurrent_unordered_map contiene un blocco quando chiama l'allocatore di memoria, le funzioni hash o altro codice definito dall'utente.Inoltre, è necessario assicurarsi che la funzione hash valuta sempre uguale tasti per lo stesso valore.Le migliori funzioni di hash distribuiscono le chiavi in modo uniforme tra lo spazio di codice hash.
Operazioni indipendenti dalla concorrenza
Il concurrent_unordered_map classe consente operazioni di inserimento e l'accesso all'elemento di concorrenza-safe.Le operazioni di inserimento non invalidano puntatori esistenti o gli iteratori.Anche le operazioni di attraversamento e di accesso iteratori sono indipendenti dalla concorrenza.Nella seguente tabella sono comunemente utilizzati concurrent_unordered_map i metodi e gli operatori che sono indipendenti dalla concorrenza.
count |
find |
||
begin |
empty |
get_allocator |
max_size |
cbegin |
end |
hash_function |
|
cend |
equal_range |
size |
Sebbene la count metodo può essere chiamato in modo sicuro dal thread in esecuzione contemporaneamente, thread diversi possono ricevere risultati diversi se un nuovo valore contemporaneamente viene inserito in un contenitore.
Nella seguente tabella sono gli operatori che non sono validi per la concorrenza e metodi comunemente utilizzati.
clear |
max_load_factor |
rehash |
load_factor |
Oltre a questi metodi, qualsiasi metodo che inizia con unsafe_ non è inoltre indipendente dalla concorrenza.
Top
Classe concurrent_unordered_multimap
Il concurrency::concurrent_unordered_multimap classe è simile al concurrent_unordered_map ad eccezione del fatto che consente di eseguire il mapping con la stessa chiave di più valori di classe.Si differenzia da concurrent_unordered_map nei seguenti modi:
Il concurrent_unordered_multimap::insert metodo restituisce un iteratore invece di std::pair<iterator, bool>.
Il concurrent_unordered_multimap classe non fornisce operator[] , né il at metodo.
Nell'esempio riportato di seguito viene illustrata la struttura di base per l'utilizzo di concurrent_unordered_multimap.In questo esempio consente di inserire caratteri di tastiera nell'intervallo ['a', ' i'].concurrent_unordered_multimapconsente a un tasto per avere più valori.
// 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]
*/
Top
Classe concurrent_unordered_set
Il concurrency::concurrent_unordered_set classe è simile al concurrent_unordered_map ad eccezione del fatto che consente di gestire i valori invece di coppie chiave / valore di classe.Il concurrent_unordered_set classe non fornisce operator[] , né il at metodo.
Nell'esempio riportato di seguito viene illustrata la struttura di base per l'utilizzo di concurrent_unordered_set.In questo esempio consente di inserire i valori di carattere nell'intervallo ['a', ' i'].Si consiglia di eseguire le operazioni di inserimento in parallelo.
// 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]
*/
Top
Classe concurrent_unordered_multiset
Il concurrency::concurrent_unordered_multiset classe simile di concurrent_unordered_set di classe con la differenza che consente valori duplicati.Si differenzia da concurrent_unordered_set nei seguenti modi:
Il concurrent_unordered_multiset::insert metodo restituisce un iteratore invece di std::pair<iterator, bool>.
Il concurrent_unordered_multiset classe non fornisce operator[] , né il at metodo.
Nell'esempio riportato di seguito viene illustrata la struttura di base per l'utilizzo di concurrent_unordered_multiset.In questo esempio consente di inserire i valori di carattere nell'intervallo ['a', ' i'].concurrent_unordered_multisetconsente a un valore ricorrere più volte.
// 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]
*/
Top
Classe combinable
Il concurrency::combinable classe fornisce riutilizzabili, thread local storage che consente di eseguire calcoli specifici e quindi unire tali calcoli in un risultato finale.È possibile considerare un oggetto combinable come una variabile di riduzione.
La classe combinable è utile quando si dispone di una risorsa condivisa tra diversi thread o attività.La classe combinable consente di eliminare stato condiviso fornendo l'accesso alle risorse condivise in modalità senza blocchi.Pertanto, questa classe fornisce un'alternativa all'utilizzo di un meccanismo di sincronizzazione, ad esempio un mutex, per sincronizzare l'accesso ai dati condivisi da più thread.
Metodi e funzionalità
Nella tabella seguente vengono illustrati alcuni dei metodi principali della classe combinable.Per ulteriori informazioni su tutti i metodi della classe combinable, vedere Classe combinable.
Metodo |
Descrizione |
---|---|
Recupera un riferimento alla variabile locale associata al contesto del thread corrente. |
|
Rimuove tutte le variabili di thread locali dall'oggetto combinable. |
|
Utilizza la funzione combine fornita per generare un valore finale dal set di tutti i calcoli di thread locali. |
La classe combinable è una classe modello contenente i parametri per il risultato finale unito.Se si chiama il costruttore predefinito, il tipo di parametro di modello _Ty deve disporre di un costruttore predefinito e un costruttore di copia.Se il tipo di parametro di modello _Ty non dispone di un costruttore predefinito, chiamare la versione di overload del costruttore che accetta una funzione di inizializzazione come parametro.
È possibile archiviare dati aggiuntivi in un oggetto combinable dopo avere chiamato i metodi combine o combine_each.I metodi combine e combine_each possono anche essere chiamati più volte.Se non viene modificato alcun valore locale in un oggetto combinable, i metodi combine e combine_each produrranno lo stesso risultato ogni volta che vengono chiamati.
Esempi
Per alcuni esempi sull'utilizzo della classe combinable, consultare gli argomenti seguenti:
Procedura: utilizzare la classe combinable per migliorare le prestazioni
Procedura: utilizzare l'oggetto combinable per combinare set
Top
Argomenti correlati
Procedura: utilizzare i contenitori paralleli per aumentare l'efficienza
Viene illustrato come utilizzare i contenitori paralleli per archiviare e accedere ai dati in parallelo in modo efficiente.Procedura: utilizzare la classe combinable per migliorare le prestazioni
Viene illustrato come utilizzare la classe combinable per eliminare stato condiviso migliorando le prestazioni.Procedura: utilizzare l'oggetto combinable per combinare set
Viene illustrato come utilizzare una funzione combine per unire set di dati di thread locali.PPL (Parallel Patterns Library)
Viene descritta la libreria PPL che fornisce un modello di programmazione imperativa in grado di offrire scalabilità e semplicità per lo sviluppo di applicazioni simultanee.
Riferimento
Classe concurrent_unordered_map
Classe concurrent_unordered_multimap
Classe concurrent_unordered_set