简体   繁体   中英

Thread-safe normal random number generator in F#

Needing a random number generator that returns a sample from a normal (Gaussian) distribution, I've ported to F# a portion of John D. Cook's C# generator :

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 () |]

This implementation works fine but is not thread safe. Given that the code that depends on it makes extensive use of the Thread Parallel Library, I need to make it thread safe.

This does not look obvious to me because at the core of the method lie two mutable members which are pretty much indispensable. Is there any other way to achieve thread-safety without resorting to locks?

Is there any other way to implement a normal pseudo-random generator using only immutable members?

Using mutable members, you'd have no choice but use a lock.

However, you'd be better off with an immutable record containing m_w and m_z that you pass to your random functions. They could return a tuple of your random value and a new record containing the updated random members. Better yet, you could create a computation expression to handle generating randoms so you won't have to worry about passing the random record around.

Also, calling setSeed from within your random functions is bad. Multiple subsequent calls will return the same vale. You only want to set your seed once.

Here's a trivial thread-safe solution using System.Random, if it's any help:

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

It's better to put all m_w / m_z and related functions into a class. Like this:

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

After that there are at least two solutions: launch every thread with its own instance of the Random object; or use ThreadLocal<Random> class for the same thing -- guaranty that every thread has own instance of the Random class.

EDIT: Also, MailboxProcessor with PostAndReply method is a good way to share single generator between threads. No need to worry about synchronization.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM