2012-12-19 6 views
16

Ho letto on Stackoverflow che nessuno dei contenitori STL è thread-safe per scrivere. Ma cosa significa in pratica? Significa che dovrei archiviare dati scrivibili in semplici array?Filo-sicurezza di scrittura di un array std :: vector vs plain

Mi aspetto che le chiamate simultanee a std::vector::push_back(element) possano portare a strutture di dati incoerenti perché potrebbero comportare il ridimensionamento del vettore. Ma che dire di un caso come questo, in cui non è coinvolto il ridimensionamento:

1) utilizzando una matrice:

int data[n]; 
// initialize values here... 

#pragma omp parallel for 
for (int i = 0; i < n; ++i) { 
    data[i] += func(i); 
} 

2) utilizzando un `std :: vector``:

std::vector<int> data; 
data.resize(n); 
// initialize values here... 

#pragma omp parallel for 
for (int i = 0; i < n; ++i) { 
    data[i] += func(i); 
} 

La prima implementazione è davvero migliore della seconda a) in termini di sicurezza dei thread eb) in termini di prestazioni? Preferirei usare un vettore std ::, dal momento che mi sento meno a mio agio con gli array in stile C.

MODIFICA: Ho rimosso un #pragma omp atomic update proteggendo la scrittura.

+1

Non sono abbastanza sicuro da dargli una risposta, ma sono abbastanza sicuro che scrivere in elementi diversi di un 'std :: vector' è sicuro per i thread. – Angew

+1

Questi due snippet sono ugualmente thread-safe. –

+0

"Ma cosa significa in pratica?" : Significa che un contenitore deve essere bloccato esclusivamente per scrivere * e * leggere se una qualsiasi operazione/coincide con una ** scrittura ** concorrente. Puoi far sbattere tutti i lettori su un contenitore che desideri, ma non appena viene introdotto un potenziale * di scrittura, tutte le scommesse sono disattivate e devi bloccare l'accesso * all * (non solo gli altri autori). Un blocco a lettura singola e multipla funziona bene per questo, btw. – WhozCraig

risposta

22

I due sono ugualmente sicuri. A condizione che nessun elemento sia accessibile da più thread sei OK. Il tuo ciclo parallelo accederà a ciascun elemento solo una volta e quindi solo da un thread.

C'è spazio nello standard per le funzioni membro dei contenitori non a prova di thread. In questo caso si utilizza vector<int>::operator[], quindi si desidera una garanzia esplicita di sicurezza del thread per quel membro, il che sembra ragionevole poiché chiamarlo anche su un vettore non const non modifica il vettore stesso. Quindi dubito che ci sia un problema in questo caso, ma non ho cercato la garanzia [modifica: rici l'ha trovato]. Anche se potenzialmente non sicuro, è possibile eseguire int *dataptr = &data.front() prima del ciclo e quindi indicizzare dataptr anziché data.

Per inciso, questo codice è non garantita sicuro per vector<bool>, poiché è uno speciale caso per cui più elementi coesistono dentro un oggetto. Sarebbe sicuro per un array di bool, poiché i diversi elementi di questo sono "posizioni di memoria" differenti (1.7 in C++ 11).

+0

Ho scritto una piccola funzione di test per vedere se openmp funzionava correttamente. Ho usato 'vector ', ridimensionato al numero di thread utilizzati (inizializzato su false) e impostato tutti i valori su true contemporaneamente. Se ogni valore era true, la funzione restituiva true per mostrare che tutti i thread stavano generando spawn correttamente. 'vector ' ha colpito ancora. Per fortuna, in realtà ho letto la parte rilevante dello standard di recente quindi ero in grado di capirlo, ma se qualcuno si imbatte in questo, hanno bisogno di assicurarsi che essi evitano le scritture simultanee al '' vettore . – Justin

+0

passando da questo, dovrebbe essere sicuro di push_back a singoli vettori sotto 'std :: vector >' da ciascun thread separato corrispondente? –

1

L'elemento principale è che se si dispone di più thread che accedono al vettore, non è possibile dipendere da C++ per evitare di danneggiare la struttura dei dati con più scritture simultanee. Quindi devi usare una specie di guardia. D'altra parte, se il tuo programma non usa più thread, come non sembrano i tuoi esempi, stai perfettamente bene.

+5

suo esempio fa uso di più thread via OpenMP – nogard

+2

upvoted a 0. Questo è stato probabilmente downvoted come non corrette, ma è stato pubblicato prima che la modifica che ha rimosso: #pragma omp aggiornamento atomica, che fa impedisce la scrittura simultanea e rende questa risposta corretta nel contesto del post originale –

1

In questo caso, è sufficiente costruire il vettore con il numero necessario di valori? e tutto andrà bene.

resize() funziona bene. La performance sarà uguale. Il motivo per cui l'accesso multithread non corromperà il vettore è: i tuoi dati si trovano nella sua posizione e non si sposteranno da lì. I thread OMP non accedono allo stesso elemento alla volta.

17

Per C++ 11, che specifica le regole per le corse dati, viene descritta la sicurezza del thread dei contenitori. Una sezione dedicata del standard è § 23.2.2, paragrafo 2:

Nonostante (17.6.5.9), le implementazioni sono tenuti ad evitare corse di dati quando il contenuto dell'oggetto contenuto in elementi diversi nella stessa sequenza, eccetto vettore <bool>, vengono modificate contemporaneamente.

[Nota: per un vettore <int> x con una dimensione maggiore di uno, x [1] = 5 e * x.begin() = 10 possono essere eseguiti contemporaneamente senza una corsa di dati, ma x [0] = 5 e * x.begin() = 10 eseguiti simultaneamente possono causare una corsa di dati. Come eccezione alla regola generale, per un vettore <bool> y, y [0] = true può correre con y [1] = vero. Nota -end]

Il citato § 17.6.5.9 vieta in sostanza qualsiasi modifica simultanea da qualsiasi interfaccia libreria standard se non specificatamente consentito, quindi la sezione cito ti dice esattamente ciò che è permesso (e che comprende l'uso).

Poiché la questione è stata sollevata da Steve Jessop, paragrafo 1 del § 23.2.2 consente esplicitamente l'uso concomitante di [] in container sequenziali:

A scopo di evitare corse di dati (17.6.5.9), implementazioni considera le seguenti funzioni come const: begin, end, rbegin, rend, front, back, data, find, lower_bound, upper_bound, equal_range, at e, tranne che in contenitori associativi o non ordinati, operator [].