2012-01-11 12 views
30

In C++ 11 ci sono un sacco di nuovi motori di generatore di numeri casuali e funzioni di distribuzione. Sono sicuri? Se condividi una singola distribuzione casuale e un motore tra più thread, è sicuro e continuerai a ricevere numeri casuali? Lo scenario sto cercando è qualcosa di simile,C++ 11 Sicurezza filo dei generatori di numeri casuali

void foo() { 
    std::mt19937_64 engine(static_cast<uint64_t> (system_clock::to_time_t(system_clock::now()))); 
    std::uniform_real_distribution<double> zeroToOne(0.0, 1.0); 
#pragma omp parallel for 
    for (int i = 0; i < 1000; i++) { 
     double a = zeroToOne(engine); 
    } 
} 

usando OpenMP o

void foo() { 
    std::mt19937_64 engine(static_cast<uint64_t> (system_clock::to_time_t(system_clock::now()))); 
    std::uniform_real_distribution<double> zeroToOne(0.0, 1.0); 
    dispatch_apply(1000, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t i) { 
     double a = zeroToOne(engine); 
    }); 
} 

utilizzando libdispatch.

risposta

25

La libreria standard C++ 11 è ampiamente thread-safe. Le garanzie di sicurezza delle filettature sugli oggetti PRNG sono le stesse dei contenitori. Più in particolare, poiché le classi PRNG sono tutte pseudo -random, ovvero generano una sequenza deterministica basata su uno stato corrente definito, non c'è davvero spazio per sbirciare o colpire qualcosa al di fuori dello stato contenuto (che è anche visibile a l'utente).

Proprio come i contenitori necessitano di blocchi per renderli sicuri da condividere, è necessario bloccare l'oggetto PRNG. Questo lo renderebbe lento e non deterministico. Un oggetto per thread sarebbe migliore.

§17.6.5.9 [res.on.data.races]:

1 Questa sezione specifica i requisiti che devono soddisfare le implementazioni per evitare corse di dati (1.10). Ogni funzione di libreria standard deve soddisfare ogni requisito, se non diversamente specificato. Le implementazioni possono impedire le corse di dati in casi diversi da quelli specificati di seguito.

2 A C++ funzione della libreria standard non deve direttamente o indirettamente oggetti di accesso (1,10) accessibili da fili diversi dalla corrente filo meno che gli oggetti sono accessibili direttamente o indirettamente tramite gli argomenti della funzione , incluso questo.

3 Una funzione libreria standard C++ non direttamente o indirettamente modificare oggetti (1.10) accessibile da fili diversi dalla corrente filo meno che gli oggetti sono accessibili direttamente o indirettamente tramite gli argomenti const non della funzione , incluso questo.

4 [Nota: Questo significa, per esempio, che le implementazioni non possono utilizzare un oggetto statico per scopi interni senza sincronizzazione perché potrebbe provocare una corsa dati anche in programmi che non condividono esplicitamente oggetti betweenthreads. -endnote] funzione della libreria standard

5 A C++ non deve accedere agli oggetti indirettamente accessibili tramite suoi argomenti o tramite elementi del suo contenitore argomenti tranne per funzioni richieste dalla specifica su tali elementi contenitori invocando.

6 Le operazioni su iteratori ottenuti chiamando una libreria standard funzione del contenitore o della stringa possono accedere al contenitore sottostante, ma non devono essere modificati. [Nota: in particolare, le operazioni del contenitore che invalidano gli iteratori sono in conflitto con le operazioni sugli iteratori associati a tale contenitore. - end note]

7 Le implementazioni possono condividere i propri oggetti interni tra i fili se gli oggetti non sono visibili agli utenti e sono protetti dalle corse di dati .

8 Salvo diversa indicazione, le funzioni di libreria standard C++ devono eseguire tutte le operazioni esclusivamente all'interno del thread corrente se quelle operazioni hanno effetti visibili (1.10) per gli utenti.

9 [Nota: questo consente alle implementazioni di parallelizzare le operazioni se non ci sono effetti collaterali visibili. - end note]

+0

Questo è in pratica quello che ho pensato che non fosse thread-safe. Va bene condividere l'oggetto distribuzione 'std :: uniform_real_distribution zeroToOne (0.0, 1.0)' importo thread e utilizzare un motore per thread? – user1139069

+0

@ user1139069: No, non sicuro. Sebbene a prima vista un oggetto di distribuzione * possa * svolgere il suo compito semplicemente delegando ogni chiamata all'oggetto motore, senza mantenere lo stato interno, se ci pensate un motore che non produce abbastanza bit casuali potrebbe dover essere chiamato due volte. Ma due volte (o una volta) può essere eccessivo, quindi potrebbe essere meglio consentire il caching di bit casuali in eccesso. §26.5.1.6 \t "Requisiti di distribuzione numeri casuali" consente questo; gli oggetti di distribuzione hanno specificatamente lo stato che cambia con ogni chiamata. Pertanto dovrebbero essere trattati come parte del motore per scopi di blocco. – Potatoswatter

0

Il documentation fa alcuna menzione di sicurezza dei thread, quindi potrebbe supporre che siano non thread-safe.

+12

Non essere menzionato su cppreference.com non lo rende non così. – Potatoswatter

2

Lo standard (be. N3242) sembra non menzionare la generazione di numeri casuali senza gara (tranne che non lo è rand), quindi non lo è (a meno che non mi sia sfuggito qualcosa). Inoltre, non c'è davvero alcun motivo per farli diventare thread, dal momento che comporterebbe un sovraccarico relativamente pesante (rispetto alla generazione dei numeri stessi almeno), senza davvero vincere nulla.

Inoltre, non vedo realmente un vantaggio con un generatore di numeri casuali condiviso, invece di avere uno per thread, ognuno dei quali è leggermente inizializzato in modo diverso (ad esempio dai risultati di un altro generatore o dall'ID corrente del thread). Dopotutto, probabilmente non ti baserai sul generatore che genera una certa sequenza, ciascuna eseguita comunque. Quindi mi sento di riscrivere il codice come qualcosa di simile (per openmp, alcun indizio circa libdispatch):

void foo() { 
    #pragma omp parallel 
    { 
    //just an example, not sure if that is a good way too seed the generation 
    //but the principle should be clear 
    std::mt19937_64 engine((omp_get_thread_num() + 1) * static_cast<uint64_t>(system_clock::to_time_t(system_clock::now()))); 
    std::uniform_real_distribution<double> zeroToOne(0.0, 1.0); 
    #pragma omp for 
     for (int i = 0; i < 1000; i++) { 
      double a = zeroToOne(engine); 
     } 
    } 
} 
+1

In realtà, se lo stesso RNG viene letto da diversi thread, * non si può * contare sull'ottenere la stessa serie di numeri casuali anche per un seme fisso perché la pianificazione può causare un ordine diverso di accesso all'RNG dai diversi thread su corse separate . Quindi * specialmente * se hai bisogno di sequenze di numeri casuali riproducibili, non dovresti condividere RNG tra i thread. – celtschk

+0

@celtschk: Dipende da come si definisce ottenere la stessa sequenza. Direi che otterremo la stessa sequenza (globaly), è solo che i thread vedranno diverse parti di esso ad ogni corsa. – Grizzly

Problemi correlati