2012-01-11 12 views
30

En C++ 11 hay un montón de nuevos motores generador de números aleatorios y funciones de distribución. ¿Están seguros? Si comparte una sola distribución aleatoria y un motor entre varios hilos, ¿es seguro y seguirá recibiendo números aleatorios? El escenario Busco es algo así como,C++ 11 Seguridad de los hilos de generadores de números aleatorios

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); 
    }); 
} 

usando libdispatch.

Respuesta

25

La biblioteca estándar de C++ 11 está ampliamente hilo de seguridad. Las garantías de seguridad de subprocesos en objetos PRNG son las mismas que en los contenedores. Más específicamente, puesto que las clases PRNG son todos seudo RANDOM, es decir, que generan una secuencia determinista basada en un estado actual definido, realmente no hay habitación para ser peeking o hurgando en nada fuera del estado contenida (que también es visible para el usuario).

Del mismo modo que los contenedores necesitan cerraduras para que sean seguros para compartir, que tendría que bloquear el objeto PRNG. Esto lo haría lento y no determinista. Un objeto por hilo sería mejor.

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

1 Esta sección especifica los requisitos que deberán cumplir las implementaciones para evitar razas de datos (1,10). Cada función de biblioteca estándar deberá cumplir con cada requisito a menos que se especifique lo contrario. Las implementaciones pueden evitar carreras de datos en casos distintos a los especificados a continuación.

2 A C++ función de biblioteca estándar no deberá directa o indirectamente objetos de acceso (1.10) accesibles por hilos distintos de la corriente hilo a menos que los objetos se accede directamente o indirectamente a través de los argumentos de la función , incluyendo este.

3 A C función de biblioteca estándar ++ no será directa o indirectamente modificar objetos (1,10) accesible por hilos distintos de la corriente hilo a menos que los objetos se accede directamente o indirectamente a través de argumentos const no de la función , incluyendo este.

4 [Nota: Esto significa, por ejemplo, que las implementaciones no pueden utilizar un objeto estático para fines internos sin sincronización, ya que podría causar una carrera de datos incluso en programas que no comparten explícitamente objetos betweenthreads. -endnote]

5 A C función de biblioteca estándar ++ no deberá acceder a los objetos indirectamente accesibles a través de sus argumentos o por medio de elementos de su contenedor argumentos excepto invocando funciones requeridas por su especificación en aquellos elementos de contenedores.

6 Las operaciones en iteradores obtenidas al llamar a una biblioteca estándar contenedor o función miembro de cadena pueden acceder al contenedor subyacente , pero no deben modificarlo. [Nota: en particular, las operaciones del contenedor que invalidan los iteradores entran en conflicto con las operaciones en los iteradores asociados con ese contenedor. - nota final]

7 Las implementaciones pueden compartir sus propios objetos internos entre los hilos si los objetos no son visibles para los usuarios y están protegidos contra datos razas.

8 Salvo que se especifique lo contrario, las funciones de biblioteca estándar de C++ deberán realizar todas las operaciones únicamente dentro del subproceso actual si esas operaciones tienen efectos que son visibles (1.10) para los usuarios.

9 [Nota: Esto permite implementaciones para paralelizar operaciones si no hay efectos secundarios visibles. - nota final]

+0

Eso es básicamente lo que pensé que no era seguro para subprocesos. ¿Está bien compartir el objeto de distribución 'std :: uniform_real_distribution zeroToOne (0.0, 1.0)' cantidad de subprocesos y usar un motor por subproceso? – user1139069

+0

@ user1139069: No, no es seguro. Aunque a primera vista un objeto de distribución * puede * hacer su trabajo simplemente delegando cada llamada al objeto del motor, sin mantener el estado interno, si lo piensa, un motor que no produce suficientes bits aleatorios podría necesitar ser llamado dos veces. Pero dos (o una) vez pueden ser excesivas, por lo que podría ser mejor permitir el almacenamiento en caché del exceso de bits aleatorios. §26.5.1.6 \t "Requisitos de distribución aleatoria de números" permite esto; Los objetos de distribución tienen específicamente un estado que cambia con cada llamada. Por lo tanto, deben tratarse como parte del motor para fines de bloqueo. – Potatoswatter

0

El documentation hace ninguna mención de hilo de seguridad, por lo que podría suponer que son no seguro para subprocesos.

+12

No mencionarlo en cppreference.com no hace que no sea así. – Potatoswatter

2

El estándar (bien N3242) parece no hacer mención de que la generación de números aleatorios sea libre de raza (excepto que rand no lo está), entonces no lo está (a menos que me haya perdido algo). Además, no tiene sentido tenerlos guardando los hilos, ya que incurriría en una sobrecarga relativamente alta (en comparación con la generación de los números al menos), sin realmente ganar nada.

Además I realmente no ver un beneficio og tener una compartida generador de números aleatorios, en lugar de tener una por hilo, estando cada uno ligeramente diferente inicializado (por ejemplo a partir de los resultados de otro generador, o la corriente de ID del tema). Después de todo, probablemente no confíe en que el generador genere una secuencia determinada de todas formas. Así que me gustaría volver a escribir el código como algo parecido a esto (por openmp, ninguna pista sobre 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

En realidad, si se lee el mismo RNG de diferentes subprocesos, * no puede * confiar en obtener la misma serie de números aleatorios incluso para una semilla fija porque la programación puede causar un orden diferente de acceso al RNG de los diferentes subprocesos en ejecuciones separadas . Entonces * especialmente * si necesita secuencias de números aleatorios reproducibles, no debe compartir RNG entre hilos. – celtschk

+0

@celtschk: Eso depende de cómo se define obtener la misma secuencia. Yo diría que uno tendrá la misma secuencia (global), es solo que los hilos verán diferentes partes con cada ejecución. – Grizzly

Cuestiones relacionadas