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