2012-05-02 18 views
5

Necesitando un generador de números aleatorios que produce una muestra de una distribución normal (gaussiana), me he portado a F # una parte de John D. Cook's C# generator:Hilo de seguridad en F #

let mutable m_w = 521288629u 
let mutable m_z = 362436069u 

let private getUint() = 
    m_z <- 36969u * (m_z &&& 65535u) + (m_z >>> 16) 
    m_w <- 18000u * (m_w &&& 65535u) + (m_w >>> 16) 
    (m_z <<< 16) + m_w 

let private setSeed() = 
    let dt = System.DateTime.Now 
    let x = dt.ToFileTime() 
    m_w <- uint32 (x >>> 16) 
    m_z <- uint32 (x % 4294967296L) 

let private getUniform() = 
    let u = getUint() 
    (float u + 1.) * 2.328306435454494e-10 

let private randomNormal() = 
    let u1 = getUniform() 
    let u2 = getUniform() 
    let r = sqrt (-2. * (log u1)) 
    let theta = 2. * System.Math.PI * u2 
    r * sin (theta) 

/// Returns a normal (Gaussian) random sample with mean 0 and standard deviation 1 
let randn() = 
    setSeed() 
    randomNormal() 

/// Returns an array of normal (Gaussian) random samples 
let randns n m = 
    setSeed() 
    [| for i in 0 .. n - 1 -> randomNormal() |] 

Esta implementación obras bien, pero no es seguro para subprocesos. Dado que el código que depende de él hace un uso extensivo de la Biblioteca de subprocesos paralelos, necesito que sea seguro para subprocesos.

Esto no me parece obvio porque en el núcleo del método se encuentran dos miembros mutables que son bastante indispensables. ¿Hay alguna otra manera de lograr seguridad de hilo sin recurrir a bloqueos?

¿Hay alguna otra forma de implementar un generador pseudoaleatorio normal utilizando solo miembros inmutables?

Respuesta

5

Al usar miembros mutables, no tendría más remedio que usar un candado.

Sin embargo, sería mejor con un inmutable record contiene m_w y m_z que se pasa a sus funciones aleatorias. Podrían devolver una tupla de su valor aleatorio y un nuevo registro que contiene los miembros aleatorios actualizados. Mejor aún, podría crear un computation expression para gestionar generación de randoms, por lo que no tendrá que preocuparse por pasar el registro aleatorio.

Además, llamar al setSeed desde sus funciones aleatorias es malo. Múltiples llamadas posteriores devolverán el mismo valor. Solo quieres establecer tu semilla una vez.

+0

Buena llamada para no llamar a setSeed todo el tiempo, supervisión estúpida –

+0

Me gusta esta idea. ¿Podría explicar la opción de expresión de cálculo (no estoy familiarizado con el concepto)? –

+0

Se vería algo así como: 'rnd { let! a = GetNext 10; deja! b = GetExponential 2.5; deja! c = GetNormal; return a, b, c } ' – IngisKahn

3

Aquí hay una solución trivial flujos seguros usando System.Random, si te sirve de ayuda:

let random = 
    let rand = System.Random() 
    let locker = obj() 
    fun() -> lock locker rand.Next 
+2

Puedes usar esto: http://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform. Convertir de números aleatorios generados por 'System.Random' a números distribuidos normalmente. –

3

Es mejor poner todas las funciones m_w/m_z y relacionadas en una clase. De esta manera:

type Random = 
    let setSeed() = ... 
    let randomNormal() = ... 

Después de que hay al menos dos soluciones: poner en marcha todos los hilos con su propia instancia del objeto Random; o use la clase ThreadLocal<Random> para la misma cosa - garantía de que cada hilo tiene su propia instancia de la clase Random.

EDIT: Además, MailboxProcessor con PostAndReply método es una buena manera de compartir un solo generador entre los hilos. No hay necesidad de preocuparse por la sincronización.

+1

El problema con la creación de una nueva instancia cada vez es que corren el riesgo de obtener la misma semilla. Creo que es muy probable que esto suceda en un escenario Array.Parallel.map (fun n -> let r = new Random() ...). –

+2

@FrancescoDeVittori: Puede incorporar datos específicos de subprocesos en su valor inicial, como 'Thread.CurrentThread.ManagedThreadId'. – Daniel

+0

@FrancescoDeVittori Sí, utilizar 'DateTime' para inicializar es una mala idea. Pero puede usar 'RNGCryptoServiceProvider.GetBytes' o algo similar para eso. – qehgt

Cuestiones relacionadas