Esporta (0) Stampa
Espandi tutto
Il presente articolo è stato tradotto automaticamente. Passare il puntatore sulle frasi nell'articolo per visualizzare il testo originale. Ulteriori informazioni.
Traduzione
Originale

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.

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.

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

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.

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.120).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]

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.

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

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.

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

operatore->

Recupera un puntatore all'elemento corrente.

[Top]

La classe concurrency::concurrent_unordered_map è una classe di contenitori associativa che, come la classe std::unordered_map, controlla una sequenza di lunghezza variabile di elementi di tipo std::pair<const Key, Ty>. Si pensi ad una mappa non ordinata come ad un dizionario dove si può aggiungere una coppia chiave-valore o si può trovare un valore per chiave. Questa classe è utile quando si dispone di più thread o attività che devono accedere simultaneamente ad un contenitore condiviso per operazioni di inserimento o di aggiornamento.

Nell'esempio seguente viene mostrata la struttura di base per l'uso di concurrent_unordered_map. In questo esempio si inseriscono i caratteri nell'intervallo ['a', 'i']. Poiché l'ordine delle operazioni è indeterminato, anche il valore finale per ogni chiave è indeterminato. Tuttavia, è possibile eseguire inserimenti in parallelo in modo sicuro.


// 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 utilizzi concurrent_unordered_map per eseguire un'operazione di mapping e di riduzione in parallelo, vedere Procedura: eseguire operazioni di mapping e riduzione in parallelo.

Dd504906.collapse_all(it-it,VS.120).gifDifferenze tra concurrent_unordered_map e unordered_map

La classe concurrent_unordered_map è molto simile alla classe unordered_map. Di seguito vengono illustrate le differenze tra concurrent_unordered_map e unordered_map:

  • I metodi erase, bucket, bucket_count e bucket_size sono denominati unsafe_erase, unsafe_bucket, unsafe_bucket_count e unsafe_bucket_size, rispettivamente. La convenzione di denominazione unsafe_ indica che questi metodi non sono indipendenti dalla concorrenza. Per ulteriori informazioni sulla sicurezza relativa alla concorrenza, vedere Operazioni indipendenti dalla concorrenza.

  • Le operazioni di inserimento non invalidano i puntatori o gli iteratori esistenti e non modificano l'ordine degli elementi già presenti nella mappa. Le operazioni di inserimento e di attraversamento possono verificarsi contemporaneamente.

  • concurrent_unordered_map supporta solo l'iterazione in avanti.

  • L'operazione di inserimento non invalida o aggiorna gli iteratori restituiti da equal_range. L'operazione di inserimento può aggiungere elementi diversi alla fine dell'intervallo. L'iteratore di inizio punta ad un elemento uguale.

Per evitare possibili deadlock, nessun metodo di concurrent_unordered_map mantiene un blocco quando chiama l'allocatore di memoria, le funzioni hash o altro codice definito dall'utente. Inoltre, è necessario assicurarsi che la funzione hash valuti sempre le chiavi uguali con lo stesso valore. Le funzioni hash migliori distribuiscono uniformemente le chiavi nello spazio del codice hash.

Dd504906.collapse_all(it-it,VS.120).gifOperazioni indipendenti dalla concorrenza

La classe concurrent_unordered_map consente le operazioni di inserimento e di accesso elementi in modo indipendente dalla concorrenza. Le operazioni di inserimento non invalidano i puntatori o gli iteratori esistenti. Anche le operazioni di attraversamento e di accesso iteratori sono indipendenti dalla concorrenza. Nella tabella seguente vengono riportati i metodi e gli operatori comunemente utilizzati di concurrent_unordered_map 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 il metodo count possa essere chiamato in modo sicuro da thread che sono in esecuzione contemporaneamente, i diversi thread possono ricevere risultati differenti se un nuovo valore viene inserito simultaneamente nel contenitore.

Nella tabella seguente vengono riportati gli operatori e i metodi comunemente utilizzati non indipendenti dalla concorrenza.

clear

max_load_factor

rehash

load_factor

operator=

scambio

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

[Top]

La classe concurrency::concurrent_unordered_multimap è molto simile alla classe concurrent_unordered_map con l'eccezione che questa consente di associare più valori alla mappa per la stessa chiave. Differisce inoltre da concurrent_unordered_map nei seguenti modi:

  • Il metodo concurrent_unordered_multimap::insert restituisce un iteratore anziché std::pair<iterator, bool>.

  • La classe concurrent_unordered_multimap non fornisce operator[] e nemmeno il metodo at.

Nell'esempio seguente viene mostrata la struttura di base per l'uso di concurrent_unordered_multimap. In questo esempio si inseriscono i caratteri nell'intervallo ['a', 'i']. concurrent_unordered_multimap consente ad 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]
*/


[Top]

La classe concurrency::concurrent_unordered_set è molto simile alla classe concurrent_unordered_map con l'unica differenza che gestisce valori anziché coppie chiave/valore. La classe concurrent_unordered_set non fornisce operator[] e nemmeno il metodo at.

Nell'esempio seguente viene mostrata la struttura di base per l'uso di concurrent_unordered_set. In questo esempio si inseriscono i valori di caratteri nell'intervallo ['a', 'i']. È possibile eseguire inserimenti in parallelo in modo sicuro.


// 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]

La classe concurrency::concurrent_unordered_multiset è molto simile alla classe concurrent_unordered_set ma consente valori duplicati. Differisce inoltre da concurrent_unordered_set nei seguenti modi:

  • Il metodo concurrent_unordered_multiset::insert restituisce un iteratore anziché std::pair<iterator, bool>.

  • La classe concurrent_unordered_multiset non fornisce operator[] e nemmeno il metodo at.

Nell'esempio seguente viene mostrata la struttura di base per l'uso di concurrent_unordered_multiset. In questo esempio si inseriscono i valori di caratteri nell'intervallo ['a', 'i']. concurrent_unordered_multiset consente ad un valore di occorrere 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]

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.

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

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

[Top]

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.

Aggiunte alla community

AGGIUNGI
Microsoft sta conducendo un sondaggio in linea per comprendere l'opinione degli utenti in merito al sito Web di MSDN. Se si sceglie di partecipare, quando si lascia il sito Web di MSDN verrà visualizzato il sondaggio in linea.

Si desidera partecipare?
Mostra:
© 2014 Microsoft