需要一个从普通(高斯)分布中返回样本的随机数生成器,我已经移植到F#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 () |]
此实现工作正常,但不是线程安全的。鉴于依赖于它的代码广泛使用了Thread Parallel Library,我需要使它成为线程安全的。
这对我来说并不明显,因为该方法的核心是两个可变成员,这几乎是不可或缺的。有没有其他方法可以在不诉诸锁定的情况下实现线程安全?
有没有其他方法可以仅使用不可变成员来实现普通的伪随机生成器?
答案 0 :(得分:5)
使用可变成员,你别无选择,只能使用锁。
但是,您最好使用包含m_w
和m_z
的不可变record传递给随机函数。他们可以返回随机值的元组和包含更新的随机成员的新记录。更好的是,您可以创建一个computation expression来处理生成的random,这样您就不必担心传递随机记录了。
另外,从随机函数中调用setSeed
是不好的。多个后续调用将返回相同的值。你只想设置一次种子。
答案 1 :(得分:3)
如果有任何帮助,这是一个使用System.Random的简单线程安全解决方案:
let random =
let rand = System.Random()
let locker = obj()
fun () -> lock locker rand.Next
答案 2 :(得分:3)
最好将所有m_w
/ m_z
及相关函数放入一个类中。像这样:
type Random =
let setSeed() = ...
let randomNormal() = ...
之后至少有两个解决方案:使用自己的Random
对象实例启动每个线程;或者使用ThreadLocal<Random>
类来做同样的事情 - 保证每个线程都有自己Random
类的实例。
编辑:
此外,使用MailboxProcessor
方法的PostAndReply
是在线程之间共享单个生成器的好方法。无需担心同步。