Condividi tramite


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.

Dd504906.collapse_all(it-it,VS.110).gifDifferenze 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.

Dd504906.collapse_all(it-it,VS.110).gifOperazioni 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.

at

end

operator[]

begin

front

push_back

back

grow_by

rbegin

capacity

grow_to_at_least

rend

empty

max_size

size

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.

assign

reserve

clear

resize

operator=

shrink_to_fit

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.

Dd504906.collapse_all(it-it,VS.110).gifSicurezza 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.

Dd504906.collapse_all(it-it,VS.110).gifDifferenze 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.

Dd504906.collapse_all(it-it,VS.110).gifOperazioni 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.

empty

push

get_allocator

try_pop

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.

clear

unsafe_end

unsafe_begin

unsafe_size

Dd504906.collapse_all(it-it,VS.110).gifSupporto 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

operator++

Si posta all'elemento successivo nella coda.Viene eseguito l'overload di questo operatore per fornire la semantica pre-incremento e post-incremento.

operator*

Recupera un riferimento all'elemento corrente.

operator->

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.

Dd504906.collapse_all(it-it,VS.110).gifUnordered_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.

Dd504906.collapse_all(it-it,VS.110).gifOperazioni 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.

at

count

find

key_eq

begin

empty

get_allocator

max_size

cbegin

end

hash_function

operator[]

cend

equal_range

Inserisci

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

operator=

scambio

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.

Dd504906.collapse_all(it-it,VS.110).gifMetodi 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

local

Recupera un riferimento alla variabile locale associata al contesto del thread corrente.

clear

Rimuove tutte le variabili di thread locali dall'oggetto combinable.

combine

combine_each

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.

Dd504906.collapse_all(it-it,VS.110).gifEsempi

Per alcuni esempi sull'utilizzo della classe combinable, consultare gli argomenti seguenti:

Top

Argomenti correlati

Riferimento

Classe concurrent_vector

Classe concurrent_queue

Classe concurrent_unordered_map

Classe concurrent_unordered_multimap

Classe concurrent_unordered_set

Classe concurrent_unordered_multiset

Classe combinable