2011-08-22 103 views
15

是否在所有線程之間共享相同的「全局隨機數生成器」,還是每個線程都有自己的?Haskell中的隨機數生成器是線程安全的嗎?

如果共享一個,我該如何確保線程安全?使用getStdGen的方法setStdGen中描述的"Monads" chapter of Real World Haskell看起來並不安全。

如果每個線程都有一個獨立的生成器,那麼快速連續啓動的兩個線程的生成器會有不同的種子嗎? (他們不會,例如,如果種子是在幾秒鐘內的時間,但毫秒可能是好的,我沒有看到如何從Data.Time獲得毫秒分辨率的時間)。

回答

14

有一個名爲newStdGen的函數,它給出了一個新的std。每次被調用時都會產生。 Its implementation使用atomicModifyIORef,因此是線程安全的。

newStdGen不僅僅是在線程安全性方面比get/setStdGen更好,它還可以防範潛在的單線程錯誤,如:let rnd = (fst . randomR (1,5)) <$> getStdGen in (==) <$> rnd <*> rnd。另外,如果你考慮newStdGengetStdGen/setStdGen的語義,第一個可以很簡單:你只需要一個新的std。在任意狀態下,非確定性地選擇。另一方面,對於get/set對,您不能抽象出由於多種原因而不好的全局程序狀態。

+0

請注意,在引擎蓋下,它使用與[FUZxxl的答案](http://stackoverflow.com/q/7153255/7153364#7153364)相同的技術; ['newStdGen''s documentation](http://hackage.haskell.org/packages/archive/random/latest/doc/html/System-Random.html#v:newStdGen)指出「[它] [a]將當前的全局隨機生成器拆分,用其中一個結果更新它,然後返回另一個,「它的實現很簡單['atomicModifyIORef theStdGen split'](http://hackage.haskell.org/packages/archive/)隨機/最新/ DOC/HTML/src目錄/系統Random.html#newStdGen)。 –

10

我會建議您只能使用getStdGen一次(在主線程中),然後使用split函數生成新的生成器。我會這樣做:

做一個MVar包含發生器。無論何時線程需要新的發生器,它將從MVar中取出當前值,調用split,並將新的發生器退回。由於MVar的功能,這應該是線程安全的。

+1

實際上,[Rotate's Rotation]中描述的['newStdGen'](http://hackage.haskell.org/packages/archive/random/latest/doc/html/System-Random.html#v:newStdGen)答案](http://stackoverflow.com/q/7153255/7155013#7155013)完全是這樣;它的文檔聲明「[它] [a]將split分配給當前的全局隨機生成器,用其中一個結果更新它,並返回另一個結果,」它的實現簡單地['atomicModifyIORef theStdGen split]](http ://hackage.haskell.org/packages/archive/random/latest/doc/html/src/System-Random.html#newStdGen)。 –

+0

哇。我不知道有這樣的功能。 – fuz

3

本身,getStdGensetStdGen在某種意義上不是線程安全的。假設兩個線程都執行此操作:

do ... 
    g <- getStdGen 
    (v, g') <- someRandOperation g 
    setStdGen g' 

這是可能的線程都運行g <- getStdGen線路的其他線程達到setStdGen之前,因此他們都可以得到完全相同的發電機。 (我錯了嗎?)

如果他們都抓住相同版本的生成器,並在相同的函數中使用它們,它們將得到相同的「隨機」結果。因此,在處理隨機數生成和多線程時,您需要小心一些。有很多解決方案;一個想到的是有一個單一的專用隨機數生成器線程,產生一個隨機數字流,其他線程可以線程安全的方式使用。按照FUZxxl的說法,將發電機放置在MVar中可能是最簡單和最直接的解決方案。

當然,我會鼓勵你檢查你的代碼,並確保它是必要在多個線程中生成隨機數。

2

您可以使用split作爲FUZxxl的答案。但是,無論何時調用forkIO,只要將分叉線程的IO操作關閉在一個生成的生成器上,並使用原始線程離開另一個生成器,就可以不使用MVar。這樣每個線程都有自己的生成器。

正如Dan Burton所說,檢查你的代碼,看看你是否真的需要多線程的RNG。