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 sicuro alla concorrenza per le operazioni più importanti. In questo caso, i puntatori o gli iteratori sicuri per la concorrenza sono sempre validi. Non è una garanzia di inizializzazione degli elementi o di un ordine attraversamento specifico. La funzionalità di questi contenitori è simile a quella fornita dalla libreria standard C++. Ad esempio, la classe concurrency::concurrent_vector è simile alla classe std::vector , ad eccezione del fatto che la concurrent_vector
classe consente di aggiungere elementi in parallelo. Usare contenitori simultanei quando si dispone di codice parallelo che richiede l'accesso in lettura e scrittura allo stesso contenitore.
Un oggetto simultaneo viene condiviso simultaneamente 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 modo seriale. La classe concurrency::combinable è un esempio di un tipo di oggetto simultaneo. La combinable
classe consente di eseguire calcoli in parallelo e quindi combinarli in un risultato finale. Usare oggetti simultanei quando in caso contrario si usa un meccanismo di sincronizzazione, ad esempio un mutex, per sincronizzare l'accesso a una variabile o a una risorsa condivisa.
Sezioni
In questo argomento vengono descritti in dettaglio i contenitori e gli oggetti paralleli seguenti.
Contenitori simultanei:
Oggetti simultanei:
Classe concurrent_vector
La classe concurrency::concurrent_vector è una classe contenitore di sequenza che, proprio come la classe std::vector , consente di accedere in modo casuale ai relativi elementi. La concurrent_vector
classe abilita le operazioni di accodamento e accesso agli elementi indipendenti dalla concorrenza. Le operazioni di accodamento non invalidano puntatori o iteratori esistenti. Anche le operazioni di accesso e attraversamento dell'iteratore sono sicure per la concorrenza. In questo caso, i puntatori o gli iteratori sicuri per la concorrenza sono sempre validi. Non è una garanzia di inizializzazione degli elementi o di un ordine attraversamento specifico.
Differenze tra concurrent_vector e vettore
La concurrent_vector
classe è simile alla vector
classe . La complessità delle operazioni di accodamento, accesso agli elementi e accesso all'iteratore in un concurrent_vector
oggetto è identica a quella di un vector
oggetto . I punti seguenti illustrano dove concurrent_vector
differisce da vector
:
Accodamento, accesso agli elementi, accesso iteratore e operazioni di attraversamento dell'iteratore in un
concurrent_vector
oggetto sono indipendenti dalla concorrenza.È possibile aggiungere elementi solo alla fine di un
concurrent_vector
oggetto . Laconcurrent_vector
classe non fornisce ilinsert
metodo .Un
concurrent_vector
oggetto non usa la semantica di spostamento quando viene accodato.La
concurrent_vector
classe non fornisce ierase
metodi opop_back
. Come convector
, usare il metodo clear per rimuovere tutti gli elementi da unconcurrent_vector
oggetto .La
concurrent_vector
classe non archivia i relativi elementi in modo contiguo in memoria. Pertanto, non è possibile usare laconcurrent_vector
classe in tutti i modi in cui è possibile usare una matrice. Ad esempio, per una variabile denominatav
di tipoconcurrent_vector
, l'espressione&v[0]+2
produce un comportamento non definito.La
concurrent_vector
classe definisce i metodi grow_by e grow_to_at_least . Questi metodi sono simili al metodo resize , ad eccezione del fatto che sono indipendenti dalla concorrenza.Un
concurrent_vector
oggetto non riloca i relativi elementi quando viene accodato o 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 tipobool
.
Operazioni sicure per la concorrenza
Tutti i metodi che aggiungono o aumentano le dimensioni di un concurrent_vector
oggetto o accedono a un elemento in un concurrent_vector
oggetto sono indipendenti dalla concorrenza. In questo caso, i puntatori o gli iteratori sicuri per la concorrenza sono sempre validi. Non è una garanzia di inizializzazione degli elementi o di un ordine attraversamento specifico. L'eccezione a questa regola è il resize
metodo .
La tabella seguente illustra i metodi e gli operatori comuni concurrent_vector
che sono sicuri per la concorrenza.
Le operazioni fornite dal runtime per la compatibilità con la libreria standard C++, ad esempio , reserve
non sono sicure per la concorrenza. La tabella seguente illustra i metodi e gli operatori comuni che non sono sicuri per la concorrenza.
Le operazioni che modificano il valore degli elementi esistenti non sono sicure per la concorrenza. Utilizzare un oggetto di sincronizzazione, ad esempio un oggetto reader_writer_lock per sincronizzare le operazioni di lettura e scrittura simultanee con lo stesso elemento dati. Per altre informazioni sugli oggetti di sincronizzazione, vedere Synchronization Data Structures.
Quando si converte il codice esistente che usa vector
per usare concurrent_vector
, le operazioni simultanee possono causare la modifica del comportamento dell'applicazione. Si consideri ad esempio il programma seguente che esegue simultaneamente due attività su un concurrent_vector
oggetto . La prima attività aggiunge elementi aggiuntivi a un concurrent_vector
oggetto . 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;
}
);
}
Anche se il end
metodo è indipendente dalla concorrenza, una chiamata simultanea al metodo push_back fa sì che il valore restituito da venga end
modificato. Il numero di elementi attraversati dall'iteratore è indeterminato. Pertanto, questo programma può produrre un risultato diverso ogni volta che viene eseguito. Quando il tipo di elemento non è semplice, è possibile che una race condition esista tra push_back
le chiamate e end
. Il end
metodo può restituire un elemento allocato, ma non completamente inizializzato.
Sicurezza delle eccezioni
Se un'operazione di crescita o assegnazione genera un'eccezione, lo stato dell'oggetto concurrent_vector
diventa non valido. Il comportamento di un concurrent_vector
oggetto in uno stato non valido non è definito se non diversamente specificato. Tuttavia, il distruttore libera sempre la memoria allocata dall'oggetto, anche se l'oggetto è in uno stato non valido.
Il tipo di dati degli elementi vettoriali, T
, deve soddisfare i requisiti seguenti. In caso contrario, il comportamento della concurrent_vector
classe non è definito.
Il distruttore non deve generare.
Se il costruttore predefinito o di copia genera un'eccezione, il distruttore non deve essere dichiarato usando la
virtual
parola chiave e deve funzionare correttamente con memoria inizializzata zero.
Classe concurrent_queue
La classe concurrency::concurrent_queue , proprio come la classe std::queue , consente di accedere ai relativi elementi anteriori e indietro. La concurrent_queue
classe abilita le operazioni di accodamento e rimozione dalla coda sicure della concorrenza. In questo caso, i puntatori o gli iteratori sicuri per la concorrenza sono sempre validi. Non è una garanzia di inizializzazione degli elementi o di un ordine attraversamento specifico. La concurrent_queue
classe fornisce anche il supporto dell'iteratore che non è sicuro per la concorrenza.
Differenze tra concurrent_queue e coda
La concurrent_queue
classe è simile alla queue
classe . I punti seguenti illustrano dove concurrent_queue
differisce da queue
:
Le operazioni di accodamento e rimozione dalla coda in un
concurrent_queue
oggetto sono sicure per la concorrenza.La
concurrent_queue
classe fornisce il supporto dell'iteratore che non è indipendente dalla concorrenza.La
concurrent_queue
classe non fornisce ifront
metodi opop
. Laconcurrent_queue
classe sostituisce questi metodi definendo il metodo try_pop .La
concurrent_queue
classe non fornisce ilback
metodo . Pertanto, non è possibile fare riferimento alla fine della coda.La
concurrent_queue
classe fornisce il metodo unsafe_size anziché ilsize
metodo . Ilunsafe_size
metodo non è indipendente dalla concorrenza.
Operazioni sicure per la concorrenza
Tutti i metodi che accodano o dequeano da un concurrent_queue
oggetto sono indipendenti dalla concorrenza. In questo caso, i puntatori o gli iteratori sicuri per la concorrenza sono sempre validi. Non è una garanzia di inizializzazione degli elementi o di un ordine attraversamento specifico.
La tabella seguente illustra i metodi e gli operatori comuni concurrent_queue
che sono sicuri per la concorrenza.
Anche se il empty
metodo è indipendente dalla concorrenza, un'operazione simultanea può causare l'aumento o la compattazione della coda prima che il empty
metodo restituisca.
La tabella seguente illustra i metodi e gli operatori comuni che non sono sicuri per la concorrenza.
Supporto iteratore
fornisce concurrent_queue
iteratori che non sono sicuri per la concorrenza. È consigliabile usare questi iteratori solo per il debug.
Un concurrent_queue
iteratore attraversa solo gli elementi nella direzione in avanti. La tabella seguente illustra gli operatori supportati da ogni iteratore.
Operatore | Descrizione |
---|---|
operator++ |
Passa all'elemento successivo nella coda. Questo operatore è sottoposto a overload per fornire la semantica di pre-incremento e post-incremento. |
operator* |
Recupera un riferimento all'elemento corrente. |
operator-> |
Recupera un puntatore all'elemento corrente. |
Classe concurrent_unordered_map
La classe concurrency::concurrent_unordered_map è una classe contenitore associativa che, analogamente alla classe std::unordered_map , controlla una sequenza di elementi di lunghezza variabile di tipo std::p air<const Key, Ty>. Si pensi a una mappa non ordinata come dizionario che è possibile aggiungere una coppia chiave e valore o cercare un valore per chiave. Questa classe è utile quando sono presenti più thread o attività che devono accedere simultaneamente a un contenitore condiviso, inserirlo o aggiornarlo.
Nell'esempio seguente viene illustrata la struttura di base per l'uso di concurrent_unordered_map
. In questo esempio vengono inseriti i tasti carattere nell'intervallo ['a', 'i']. Poiché l'ordine delle operazioni non è deterministico, anche il valore finale per ogni chiave è indeterminato. Tuttavia, è sicuro eseguire gli inserimenti 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 che usa concurrent_unordered_map
per eseguire un'operazione di mapping e riduzione in parallelo, vedere Procedura: Eseguire operazioni di mapping e riduzione in parallelo.
Differenze tra concurrent_unordered_map e unordered_map
La concurrent_unordered_map
classe è simile alla unordered_map
classe . I punti seguenti illustrano dove concurrent_unordered_map
differisce da unordered_map
:
I
erase
metodi ,bucket
,bucket_count
ebucket_size
sono denominatiunsafe_erase
rispettivamente ,unsafe_bucket_count
unsafe_bucket
, eunsafe_bucket_size
. Launsafe_
convenzione di denominazione indica che questi metodi non sono sicuri per la concorrenza. Per altre informazioni sulla sicurezza della concorrenza, vedere Operazioni sicure per la concorrenza.Le operazioni di inserimento non invalidano puntatori o iteratori esistenti, né modificano l'ordine degli elementi già presenti nella mappa. Le operazioni di inserimento e attraversamento possono verificarsi simultaneamente.
concurrent_unordered_map
supporta solo l'iterazione in avanti.L'inserimento non invalida o aggiorna gli iteratori restituiti da
equal_range
. L'inserimento può aggiungere elementi diversi alla fine dell'intervallo. L'iteratore di inizio punta a un elemento uguale.
Per evitare deadlock, nessun metodo di contiene un blocco quando chiama l'allocatore di concurrent_unordered_map
memoria, le funzioni hash o altro codice definito dall'utente. Inoltre, è necessario assicurarsi che la funzione hash restituisca sempre chiavi uguali allo stesso valore. Le migliori funzioni hash distribuiscono le chiavi in modo uniforme nello spazio del codice hash.
Operazioni sicure per la concorrenza
La concurrent_unordered_map
classe abilita le operazioni di inserimento ed accesso agli elementi sicure per la concorrenza. Le operazioni di inserimento non invalidano puntatori o iteratori esistenti. Anche le operazioni di accesso e attraversamento dell'iteratore sono sicure per la concorrenza. In questo caso, i puntatori o gli iteratori sicuri per la concorrenza sono sempre validi. Non è una garanzia di inizializzazione degli elementi o di un ordine attraversamento specifico. La tabella seguente illustra i metodi e gli operatori comunemente usati concurrent_unordered_map
che sono sicuri per la concorrenza.
Anche se il count
metodo può essere chiamato in modo sicuro da thread in esecuzione simultanea, i thread diversi possono ricevere risultati diversi se un nuovo valore viene inserito contemporaneamente nel contenitore.
Nella tabella seguente vengono illustrati i metodi e gli operatori comunemente usati che non sono sicuri per la concorrenza.
Oltre a questi metodi, qualsiasi metodo che inizia con unsafe_
non è sicuro per la concorrenza.
Classe concurrent_unordered_multimap
La classe concurrency::concurrent_unordered_multimap è molto simile alla concurrent_unordered_map
classe , ad eccezione del fatto che consente il mapping di più valori alla stessa chiave. Differisce anche da concurrent_unordered_map
nei modi seguenti:
Il metodo concurrent_unordered_multimap::insert restituisce un iteratore anziché
std::pair<iterator, bool>
.La
concurrent_unordered_multimap
classe non fornisceoperator[]
né ilat
metodo .
Nell'esempio seguente viene illustrata la struttura di base per l'uso di concurrent_unordered_multimap
. In questo esempio vengono inseriti i tasti carattere nell'intervallo ['a', 'i']. concurrent_unordered_multimap
consente a una chiave di 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]
*/
Classe concurrent_unordered_set
La classe concurrency::concurrent_unordered_set è molto simile alla concurrent_unordered_map
classe, ad eccezione del fatto che gestisce i valori anziché coppie chiave e valore. La concurrent_unordered_set
classe non fornisce operator[]
né il at
metodo .
Nell'esempio seguente viene illustrata la struttura di base per l'uso di concurrent_unordered_set
. In questo esempio vengono inseriti valori di carattere nell'intervallo ['a', 'i']. È sicuro eseguire gli inserimenti 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]
*/
Classe concurrent_unordered_multiset
La classe concurrency::concurrent_unordered_multiset è molto simile alla classe, ad eccezione del concurrent_unordered_set
fatto che consente valori duplicati. Differisce anche da concurrent_unordered_set
nei modi seguenti:
Il metodo concurrent_unordered_multiset::insert restituisce un iteratore anziché
std::pair<iterator, bool>
.La
concurrent_unordered_multiset
classe non fornisceoperator[]
né ilat
metodo .
Nell'esempio seguente viene illustrata la struttura di base per l'uso di concurrent_unordered_multiset
. In questo esempio vengono inseriti valori di carattere nell'intervallo ['a', 'i']. concurrent_unordered_multiset
consente a un valore di verificarsi 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]
*/
Classe combinable
La classe concurrency::combinable fornisce risorse di archiviazione riutilizzabili e locali del thread che consentono di eseguire calcoli con granularità fine e quindi unire tali calcoli in un risultato finale. È possibile considerare un oggetto combinable
come una variabile di riduzione.
La combinable
classe è utile quando si dispone di una risorsa condivisa tra più thread o attività. La combinable
classe consente di eliminare lo stato condiviso fornendo l'accesso alle risorse condivise in modo senza blocchi. Pertanto, questa classe offre un'alternativa all'uso di un meccanismo di sincronizzazione, ad esempio un mutex, per sincronizzare l'accesso ai dati condivisi da più thread.
Metodi e funzionalità
La tabella seguente illustra alcuni dei metodi importanti della combinable
classe . Per altre informazioni su tutti i metodi di combinable
classe, vedere Classe combinabile.
metodo | Descrizione |
---|---|
local | Recupera un riferimento alla variabile locale associata al contesto del thread corrente. |
deselezionare | Rimuove tutte le variabili locali del thread dall'oggetto combinable . |
combine combine_each |
Usa la funzione di combinazione fornita per generare un valore finale dal set di tutti i calcoli locali del thread. |
La combinable
classe è una classe modello con parametri per il risultato finale unito. Se si chiama il costruttore predefinito, il T
tipo di parametro del modello deve avere un costruttore predefinito e un costruttore di copia. Se il T
tipo di parametro del modello 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 combinable
oggetto dopo aver chiamato i metodi combine o combine_each . È anche possibile chiamare i combine
metodi e combine_each
più volte. Se non viene modificato alcun valore locale in un combinable
oggetto, i combine
metodi e combine_each
producono lo stesso risultato ogni volta che vengono chiamati.
Esempi
Per esempi su come usare la combinable
classe , vedere gli argomenti seguenti:
Argomenti correlati
Procedura: Usare i contenitori paralleli per aumentare l'efficienza
Illustra come usare contenitori paralleli per archiviare e accedere in modo efficiente ai dati in parallelo.
Procedura: Usare la classe combinable per migliorare le prestazioni
Illustra come usare la combinable
classe per eliminare lo stato condiviso e quindi migliorare le prestazioni.
Procedura: Usare l'oggetto combinable per combinare set
Illustra come usare una combine
funzione per unire set di dati locali di thread.
PPL (Parallel Patterns Library)
Descrive il PPL, che fornisce un modello di programmazione imperativo che promuove scalabilità e facilità d'uso per lo sviluppo di applicazioni simultanee.
Riferimento
Classe concurrent_unordered_map
Classe concurrent_unordered_multimap
Classe concurrent_unordered_set