【问题标题】:Is the random number generator in Haskell thread-safe?Haskell 中的随机数生成器是线程安全的吗?
【发布时间】:2011-08-22 20:32:12
【问题描述】:
是所有线程都共享同一个“全局随机数生成器”,还是每个线程都有自己的?
如果是共享的,如何确保线程安全? "Monads" chapter of Real World Haskell 中描述的使用 getStdGen 和 setStdGen 的方法看起来并不安全。
如果每个线程都有一个独立的生成器,那么快速连续启动的两个线程的生成器是否会有不同的种子? (例如,如果种子是以秒为单位的时间,他们不会,但毫秒可能没问题。我不知道如何从 Data.Time 获得毫秒分辨率的时间。)
【问题讨论】:
标签:
multithreading
random
thread-safety
haskell
【解决方案1】:
有一个名为newStdGen 的函数,它提供了一个新的标准。 gen 每次被调用。 Its implementation 使用 atomicModifyIORef,因此是线程安全的。
newStdGen 不仅在线程安全方面比 get/setStdGen 更好,而且还可以保护您免受像这样的潜在单线程错误:let rnd = (fst . randomR (1,5)) <$> getStdGen in (==) <$> rnd <*> rnd。
此外,如果您考虑 newStdGen 与 getStdGen/setStdGen 的语义,第一个可能非常简单:您只需获得一个新的 std。 gen 处于随机状态,非确定性地选择。另一方面,使用 get/set 对,您无法抽象出全局程序状态,这有多种原因是不好的。
【解决方案2】:
我建议您只使用一次getStdGen(在主线程中),然后使用split 函数生成新的生成器。我会这样做:
创建一个包含生成器的MVar。每当一个线程需要一个新的生成器时,它就会从MVar 中取出当前值,调用split 并将新的生成器放回去。由于MVar 的功能,这应该是线程安全的。
【解决方案3】:
就其本身而言,getStdGen 和 setStdGen 在某种意义上不是线程安全的。假设两个线程都执行这个动作:
do ...
g <- getStdGen
(v, g') <- someRandOperation g
setStdGen g'
线程有可能在另一个线程到达setStdGen 之前都运行g <- getStdGen 行,因此它们都可以获得完全相同的生成器。 (我错了吗?)
如果它们都抓取相同版本的生成器,并在相同的函数中使用它,它们将得到相同的“随机”结果。因此,在处理随机数生成和多线程时,您确实需要更加小心。有很多解决方案;想到的一个是有一个专用的随机数生成器线程,它产生一个随机数流,其他线程可以以线程安全的方式使用这些随机数。正如 FUZxxl 所建议的,将生成器放在 MVar 中可能是最简单、最直接的解决方案。
当然,我鼓励您检查您的代码并确保有必要在多个线程中生成随机数。
【解决方案4】:
您可以在 FUZxxl 的回答中使用 split。但是,当您调用forkIO 时,不要使用 MVar,只需让分叉线程的 IO 操作关闭其中一个生成的生成器,而将另一个留给原始线程。这样每个线程都有自己的生成器。
正如 Dan Burton 所说,请检查您的代码,看看您是否真的需要多线程中的 RNG。