5

需要一个从正态(高斯)分布返回样本的随机数生成器,我已将John D. Cook 的 C# 生成器的一部分移植到 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 () |]

此实现工作正常,但不是线程安全的。鉴于依赖于它的代码广泛使用了线程并行库,我需要使其成为线程安全的。

这对我来说并不明显,因为该方法的核心是两个可变成员,它们几乎是不可或缺的。有没有其他方法可以在不使用锁的情况下实现线程安全?

有没有其他方法可以仅使用不可变成员来实现普通的伪随机生成器?

4

3 回答 3

5

使用可变成员,您别无选择,只能使用锁。

但是,您最好使用包含的不可变记录并将m_wm_z传递给您的随机函数。他们可以返回您的随机值的元组和包含更新的随机成员的新​​记录。更好的是,您可以创建一个计算表达式来处理生成随机数,这样您就不必担心传递随机记录。

此外,setSeed从您的随机函数中调用是不好的。多个后续调用将返回相同的值。您只想设置一次种子。

于 2012-05-02T16:09:15.933 回答
3

这是一个使用 System.Random 的简单线程安全解决方案,如果有帮助的话:

let random =
  let rand = System.Random()
  let locker = obj()
  fun () -> lock locker rand.Next
于 2012-05-02T16:27:04.097 回答
3

最好将所有m_w/m_z和相关函数放入一个类中。像这样:

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

之后至少有两种解决方案:启动每个线程并使用它自己的Random对象实例;或者使用ThreadLocal<Random>类来做同样的事情——保证每个线程都有自己的Random类实例。

编辑:另外,MailboxProcessorwithPostAndReply方法是在线程之间共享单个生成器的好方法。无需担心同步。

于 2012-05-02T17:06:21.157 回答