全局随机数生成器是共享的吗?还是每个线程都有自己的随机数生成器?
如果是共享的,如何确保线程安全?在《Real World Haskell》的"单子"章节中描述的使用getStdGen和setStdGen的方法似乎不安全。
如果每个线程都有独立的生成器,那么在快速连续启动两个线程时,它们的生成器会具有不同的种子吗?(例如,如果种子是以秒为单位的时间,则不会,但毫秒可能可以。我不知道如何从Data.Time中获取毫秒分辨率的时间。)
全局随机数生成器是共享的吗?还是每个线程都有自己的随机数生成器?
如果是共享的,如何确保线程安全?在《Real World Haskell》的"单子"章节中描述的使用getStdGen和setStdGen的方法似乎不安全。
如果每个线程都有独立的生成器,那么在快速连续启动两个线程时,它们的生成器会具有不同的种子吗?(例如,如果种子是以秒为单位的时间,则不会,但毫秒可能可以。我不知道如何从Data.Time中获取毫秒分辨率的时间。)
有一个名为newStdGen
的函数,每次调用该函数都会提供一个新的std. gen。 它的实现使用了atomicModifyIORef
,因此是线程安全的。
newStdGen
不仅在线程安全方面比get/setStdGen更好,还可以防止潜在的单线程错误,例如:let rnd = (fst . randomR (1,5)) <$> getStdGen in (==) <$> rnd <*> rnd
.
此外,如果考虑newStdGen
与getStdGen
/setStdGen
之间的语义,前者可能非常简单:您只需获得具有随机状态的新std. gen,选择非确定性。另一方面,如果使用get/set配对,则无法抽象出全局程序状态,这对多个原因都很糟糕。
我建议你只在主线程中使用getStdGen
一次,然后使用split
函数生成新的生成器。我会这样做:
创建一个包含生成器的MVar
。每当一个线程需要一个新的生成器时,它从MVar
中取出当前值,调用split
,并将新生成器放回。由于MVar
的功能,这应该是线程安全的。
newStdGen
操作正是这样做的;它的文档说明:“[它]将split
应用于当前全局随机生成器,使用其中一个结果更新它,并返回另一个”,其实现只是atomicModifyIORef theStdGen split
。 - Antal Spector-ZabuskygetStdGen
和setStdGen
在某种意义上不是线程安全的。假设两个线程都执行此操作:
do ...
g <- getStdGen
(v, g') <- someRandOperation g
setStdGen g'
你可以像FUZxxl的回答一样使用split
。但是,不要使用MVar,每当调用forkIO
时,只需让分叉线程的IO操作关闭其中一个生成器,并将另一个生成器保留在原始线程中。这样每个线程都有自己的生成器。
正如Dan Burton所说,检查您的代码,看看是否真的需要在多个线程中使用RNG。
newStdGen
的文档指出:“[它]将分裂应用于当前全局随机生成器,使用其中一个结果更新它,并返回另一个”,其实现仅为atomicModifyIORef theStdGen split
。 - Antal Spector-Zabusky