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, la classe Concurrency::concurrent_vector è simile alla classe std::vector, ad eccezione del fatto che la classe concurrent_vector consente di accodare gli 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. La classe Concurrency::combinable è un esempio di un tipo di oggetto simultaneo. 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

  • Classe concurrent_queue

Oggetti simultanei:

  • Classe combinable

Classe concurrent_vector

La classe Concurrency::concurrent_vector è una classe di contenitori di sequenza che analogamente alla classe std::vector consente di accedere in modo casuale ai relativi 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.

at

end

operator[]

begin

front

push_back

back

grow_by

rbegin

capacity

grow_to_at_least

rend

empty

max_size

size

Le operazioni fornite dal runtime per la compatibilità con STL, ad esempio reserve, non sono indipendenti dalla 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 = v.begin(); i != v.end(); ++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.

[vai all'inizio]

Classe concurrent_queue

La classe Concurrency::concurrent_queue, analogamente alla classe std::queue, consente di accedere ai relativi elementi anteriore e posteriore. 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.

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

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

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.

[vai all'inizio]

Classe combinable

La classe Concurrency::combinable fornisce l'archiviazione locale dei thread riutilizzabile che consente di eseguire calcoli con granularità fine e quindi di 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

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.

Esempi

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

[vai all'inizio]

Argomenti correlati

Riferimenti

Classe concurrent_vector

Classe concurrent_queue

Classe combinable