We recommend using Visual Studio 2017

Contenitori e oggetti paralleli

 

Data di pubblicazione: aprile 2016

Per la documentazione più recente di Visual Studio 2017 RC, vedere Documentazione di Visual Studio 2017 RC.

La libreria PPL (Parallel Patterns Library) include diversi contenitori e oggetti che forniscono l'accesso thread-safe ai relativi elementi.

Oggetto contenitore simultaneo fornisce l'accesso sicuro di concorrenza per le operazioni più importanti. La funzionalità di questi contenitori è simile a quelle fornite dalla libreria di modelli Standard (STL). Ad esempio, il concurrent_vector classe è simile al std:: Vector classe, con la differenza che la concurrent_vector classe consente di accodare gli elementi in parallelo. Utilizzare i contenitori simultanei quando si dispone di codice parallelo che richiede l'accesso in lettura e scrittura nello stesso contenitore.

Oggetto 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 modo seriale. Il Concurrency:: combinable classe è un esempio di un tipo di oggetto simultaneo. La combinable classe consente di eseguire calcoli in parallelo e quindi combinare tali calcoli in un risultato finale. Utilizzare gli oggetti simultanei quando in caso contrario, è necessario utilizzare un meccanismo di sincronizzazione, ad esempio, un mutex, per sincronizzare l'accesso a una variabile condivisa o una risorsa.

In questo argomento vengono descritti i seguenti contenitori e oggetti paralleli in dettaglio.

Contenitori simultanei:

Oggetti simultanei:

Il concurrent_vector classe è una classe contenitore di sequenza che, come il std:: Vector classe, consente di accedere in modo casuale ai relativi elementi. La concurrent_vector classe consente di aggiungere indipendenti dalla concorrenza e di elemento di accedere alle operazioni. Aggiungere le operazioni non invalidano i puntatori esistenti o gli iteratori. Le operazioni di accesso e l'attraversamento di iteratore sono indipendenti dalla concorrenza.

Differenze tra concurrent_vector e Vector

La concurrent_vector classe rispecchia maggiormente la vector classe. La complessità di accodamento, l'accesso agli elementi e operazioni di accesso iteratore un concurrent_vector oggetto sono le stesse di una vector oggetto. I punti seguenti illustrano dove concurrent_vector è diverso da vector:

  • Aggiungere l'accesso agli elementi, iteratori e le operazioni di attraversamento di iteratore in un concurrent_vector oggetto sono indipendenti dalla concorrenza.

  • È possibile aggiungere elementi solo fino alla fine di un concurrent_vector oggetto. La concurrent_vector classe non fornisce il insert metodo.

  • Oggetto concurrent_vector non utilizza l'oggetto la semantica di spostamento quando si aggiunge a esso.

  • La concurrent_vector classe non fornisce il erase o pop_back metodi. Come con vector, utilizzare il deselezionare metodo per rimuovere tutti gli elementi da un concurrent_vector oggetto.

  • La concurrent_vector classe archivia i relativi elementi in modo contiguo nella memoria. Pertanto, è possibile utilizzare il concurrent_vector (classe) in tutte le modalità che è 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 concurrent_vector classe definisce il grow_by e grow_to_at_least metodi. Questi metodi sono simili di ridimensionare metodo, ad eccezione del fatto che siano indipendenti dalla concorrenza.

  • Oggetto concurrent_vector oggetto non consente di spostare gli elementi quando si aggiungervi o ridimensionarlo. In questo modo ai puntatori esistenti e gli iteratori rimangono validi durante le operazioni simultanee.

  • Il runtime non definisce una versione specializzata di concurrent_vector per tipo bool.

Operazioni indipendenti dalla concorrenza

Tutti i metodi che aggiungere o aumentano le dimensioni di un concurrent_vector dell'oggetto o accedere a un elemento in un concurrent_vector di oggetto, sono indipendenti dalla concorrenza. L'eccezione a questa regola è il resize metodo.

La tabella seguente illustra i comuni concurrent_vector metodi e gli operatori che sono indipendenti dalla concorrenza.

infineoperatore [ ]
iniziareprimo pianopush_back
Indietrogrow_byrbegin
capacitàgrow_to_at_leastREND)
vuotomax_sizedimensioni

Operazioni offerte dal runtime per la compatibilità con la libreria STL, ad esempio, reserve, non sono indipendenti dalla concorrenza. La tabella seguente illustra i metodi e operatori che non sono indipendenti dalla concorrenza comuni.

assegnareriserva
deselezionareridimensionamento
operatore =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 reader_writer_lock oggetto di sincronizzazione simultanee di lettura e operazioni di scrittura allo stesso elemento di dati. Per ulteriori informazioni sugli oggetti di sincronizzazione, vedere strutture di dati di sincronizzazione.

Quando si converte codice esistente che utilizza vector utilizzare concurrent_vector, operazioni simultanee possono causare il comportamento dell'applicazione di modifica. Si consideri ad esempio il programma seguente che vengono eseguite contemporaneamente due attività in un concurrent_vector oggetto. La prima attività accoda 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 sia indipendente dalla concorrenza, una chiamata simultanea di push_back (metodo), il valore restituito da end per modificare. Il numero di elementi che attraversa l'iteratore è indeterminato. Pertanto, questo programma può produrre un risultato diverso ogni volta che viene eseguito.

Sicurezza dell'eccezione

Se un'operazione di aumento delle dimensioni o assegnazione genera un'eccezione, lo stato di concurrent_vector oggetto diventa non valido. Il comportamento di un concurrent_vector oggetto in uno stato non valido è definito se non specificato diversamente. 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 del vettore, T, deve soddisfare i requisiti seguenti. In caso contrario, il comportamento della concurrent_vector classe è definita.

  • Il distruttore non deve generare.

  • Se il costruttore predefinito o di copia genera, il distruttore non deve essere dichiarato tramite il virtual (parola chiave) e deve funzionare correttamente con memoria inizializzate su zero.

[Torna all'inizio]

Il Concurrency:: concurrent_queue classe, come il std:: Queue classe, consente di accedere il primo piano e il backup di elementi. Il concurrent_queue enqueue indipendenti dalla concorrenza consente di classe e le operazioni di rimozione dalla coda. La concurrent_queue classe fornisce inoltre il supporto iteratori non indipendenti dalla concorrenza.

Differenze tra concurrent_queue e coda

La concurrent_queue classe rispecchia maggiormente la queue classe. I punti seguenti illustrano dove concurrent_queue è diverso da queue:

  • Enqueue e dequeue operazioni su un concurrent_queue oggetto sono indipendenti dalla concorrenza.

  • La concurrent_queue classe fornisce il supporto iteratori non indipendenti dalla concorrenza.

  • La concurrent_queue classe non fornisce il front o pop metodi. La concurrent_queue classe sostituisce questi metodi definendo il try_pop metodo.

  • La concurrent_queue classe non fornisce il back metodo. Pertanto, è possibile fare riferimento la fine della coda.

  • La concurrent_queue classe fornisce il unsafe_size metodo invece di size (metodo). Il unsafe_size metodo non è indipendente dalla concorrenza.

Operazioni indipendenti dalla concorrenza

Tutti i metodi che accodano dati a o rimuovere dalla coda da un concurrent_queue oggetto sono indipendenti dalla concorrenza.

La tabella seguente illustra i comuni concurrent_queue metodi e gli operatori che sono indipendenti dalla concorrenza.

vuotopush
get_allocatortry_pop

Sebbene il empty metodo sia indipendente dalla concorrenza, un'operazione simultanea potrebbe determinare la coda aumentare o ridurre la prima di empty restituzione del metodo.

La tabella seguente illustra i metodi e operatori che non sono indipendenti dalla concorrenza comuni.

deselezionareunsafe_end
unsafe_beginunsafe_size

Supporto degli iteratori

Il concurrent_queue fornisce gli iteratori che non sono indipendenti dalla concorrenza. È consigliabile utilizzare questi iteratori solo per il debug.

Oggetto concurrent_queue iteratore consente di scorrere gli elementi nella direzione dell'inoltro. La tabella seguente illustra gli operatori supportati da ciascuno di essi.

OperatoreDescrizione
operator + +Avanza all'elemento successivo nella coda. Questo operatore viene sottoposto a overload per fornire la semantica di pre-incremento e post-incremento.
operatore *Recupera un riferimento all'elemento corrente.
operator ->Recupera un puntatore all'elemento corrente.

[Torna all'inizio]

Il HYPERLINK "file:///C:\\Users\\thompet\\AppData\\Local\\Temp\\DxEditor\\DduePreview\\Default\\798d7037-df37-4310-858b-6f590bbf6ebf\\HTM\\html\\a217b4ac-af2b-4d41-94eb-09a75ee28622" concurrency::concurrent_unordered_map è una classe contenitore associativo che, come il std:: unordered_map classe, controlla 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 a 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 al suo interno o aggiornarlo contemporaneamente.

Nell'esempio seguente viene illustrata la struttura di base per l'utilizzo di concurrent_unordered_map. In questo esempio inserisce le chiavi di caratteri nell'intervallo ['a', ' i']. Poiché non è possibile stabilire l'ordine delle operazioni, il valore finale per ogni chiave è indeterminato. Tuttavia, è consigliabile eseguire 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 utilizza concurrent_unordered_map per eseguire una mappa e ridurre l'operazione in parallelo, vedere procedura: eseguire mappa e ridurre le operazioni in parallelo.

Unordered_map e differenze tra concurrent_unordered_map

La concurrent_unordered_map classe rispecchia maggiormente la unordered_map classe. I punti seguenti illustrano dove concurrent_unordered_map è diverso da unordered_map:

  • Il erase, bucket, bucket_count, e bucket_size sono denominati metodi unsafe_erase, unsafe_bucket, unsafe_bucket_count, e unsafe_bucket_size, rispettivamente. Il unsafe_ convenzione di denominazione indica che questi metodi non sono indipendenti dalla concorrenza. Per ulteriori informazioni sulla protezione di concorrenza, vedere operazioni indipendenti dalla concorrenza.

  • Le operazioni di inserimento non invalidano i puntatori esistenti o gli iteratori, né modificano l'ordine degli elementi già presenti nella mappa. Inserire e attraversare le operazioni possono venire generati contemporaneamente.

  • concurrent_unordered_map supporta inoltra solo l'iterazione.

  • Inserimento non invalidano né aggiornare gli iteratori che sono restituiti da equal_range. Inserimento è possibile aggiungere diversi elementi alla fine dell'intervallo. L'iteratore begin punta a un elemento uguale.

Per evitare deadlock, alcun metodo di concurrent_unordered_map quando chiama l'allocatore di memoria, le funzioni hash o altro codice definito dall'utente è responsabile del blocco. Inoltre, è necessario verificare che la funzione hash valuta sempre uguale chiavi per lo stesso valore. Le funzioni hash migliore di distribuire le chiavi in modo uniforme tra lo spazio di codice hash.

Operazioni indipendenti dalla concorrenza

La concurrent_unordered_map classe consente di eseguire operazioni insert e accesso agli elementi indipendenti dalla concorrenza. Le operazioni di inserimento non invalidano i puntatori esistenti o gli iteratori. Le operazioni di accesso e l'attraversamento di iteratore sono indipendenti dalla concorrenza. Nella tabella seguente vengono usati comunemente concurrent_unordered_map metodi e gli operatori che sono indipendenti dalla concorrenza.

incountfindkey_eq
beginemptyget_allocatormax_size
cbeginendhash_functionoperatore [ ]
cendequal_rangeinserimentosize

Sebbene il count metodo può essere chiamato in modo sicuro dall'esecuzione simultanea di thread, thread diversi possono ottenere risultati diversi se un nuovo valore contemporaneamente è inserito nel contenitore.

La tabella seguente illustra i metodi comunemente utilizzati e gli operatori che non sono indipendenti dalla concorrenza.

clearmax_load_factorrehash
load_factoroperatore =swap

Oltre a questi metodi, qualsiasi metodo che inizia con unsafe_ non è inoltre indipendenti dalla concorrenza.

[Torna all'inizio]

Il concurrency::concurrent_unordered_multimap classe simile di concurrent_unordered_map ad eccezione del fatto che consente di eseguire il mapping alla stessa chiave di più valori. È inoltre diverso da concurrent_unordered_map nei modi seguenti:

Nell'esempio seguente viene illustrata la struttura di base per l'utilizzo di concurrent_unordered_multimap. In questo esempio inserisce le chiavi di caratteri nell'intervallo ['a', ' i']. concurrent_unordered_multimap consente a una chiave 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]
*/

[Torna all'inizio]

Il concurrency::concurrent_unordered_set classe ricorda la concurrent_unordered_map classe con la differenza che gestisce i valori invece di coppie chiave / valore. La concurrent_unordered_set classe non fornisce operator[]at metodo.

Nell'esempio seguente viene illustrata la struttura di base per l'utilizzo di concurrent_unordered_set. In questo esempio inserisce i valori di carattere nell'intervallo ['a', ' i']. È consigliabile eseguire 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]
*/

[Torna all'inizio]

Il concurrency::concurrent_unordered_multiset classe simile di concurrent_unordered_set ad eccezione del fatto che consente valori duplicati. È inoltre diverso da concurrent_unordered_set nei modi seguenti:

Nell'esempio seguente viene illustrata la struttura di base per l'utilizzo di concurrent_unordered_multiset. In questo esempio inserisce i valori di carattere nell'intervallo ['a', ' i']. concurrent_unordered_multiset consente un valore di 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]
*/

[Torna all'inizio]

Il Concurrency:: combinable classe fornisce l'archiviazione locale di thread e riutilizzabile che consente 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 che viene condiviso tra diversi thread o attività. La combinable classe consente di eliminare lo stato condiviso fornendo accesso alle risorse condivise in una 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 importanti della combinable classe. Per ulteriori informazioni su tutti i combinable metodi della classe, vedere classe combinable.

MetodoDescrizione
localeRecupera un riferimento alla variabile locale associata al contesto del thread corrente.
deselezionareRimuove tutte le variabili locali del thread dal combinable oggetto.
combinare

 combine_each
Utilizza la funzione combine fornita per generare un valore finale dal set di tutti i calcoli locali del thread.

La combinable classe è una classe di modello contenente i parametri per il risultato finale unito. Se si chiama il costruttore predefinito, il T il tipo di parametro di modello deve avere un costruttore predefinito e un costruttore di copia. Se il T il tipo di parametro di 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 dell'oggetto dopo la chiamata di combinare o combine_each metodi. È inoltre possibile chiamare il combine e combine_each metodi più volte. Se nessun valore locale in un combinable oggetto viene modificato, il combine e combine_each metodi producono lo stesso risultato ogni volta che vengono chiamati.

Esempi

Per esempi su come utilizzare la combinable classe, vedere gli argomenti seguenti:

[Torna all'inizio]

Procedura: utilizzare i contenitori paralleli per aumentare l'efficienza
Viene illustrato come utilizzare i contenitori paralleli per archiviare in modo efficiente e accedere ai dati in parallelo.

Procedura: utilizzare la classe combinable per migliorare le prestazioni
Viene illustrato come utilizzare la combinable classe per eliminare lo stato condiviso e migliorare le prestazioni.

Procedura: utilizzare la classe combinable per combinare set
Viene illustrato come utilizzare un combine funzione per set di dati locali di thread di tipo merge.

Parallel Patterns Library (PPL)
Viene descritta la libreria PPL, che fornisce un modello di programmazione imperativa in grado di offrire scalabilità e facilità di utilizzo per lo sviluppo di applicazioni simultanee.

Classe concurrent_vector

Classe concurrent_queue

Classe concurrent_unordered_map

Classe concurrent_unordered_multimap

Classe concurrent_unordered_set

Classe concurrent_unordered_multiset

combinable (classe)

Mostra: