簡體   English   中英

F#中的線程安全正常隨機數發生器

[英]Thread-safe normal random number generator in F#

需要一個從普通(高斯)分布返回樣本的隨機數生成器,我已經移植到F#作為John D. Cook的C#生成器的一部分

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,我需要使它成為線程安全的。

這對我來說並不明顯,因為該方法的核心是兩個可變成員,這幾乎是不可或缺的。 有沒有其他方法來實現線程安全而不訴諸鎖定?

有沒有其他方法來實現僅使用不可變成員的普通偽隨機生成器?

使用可變成員,你別無選擇,只能使用鎖。

但是,你最好使用包含m_wm_z的不可變記錄傳遞給隨機函數。 他們可以返回隨機值的元組和包含更新的隨機成員的新​​記錄。 更好的是,您可以創建一個計算表達式來處理生成的random,這樣您就不必擔心傳遞隨機記錄了。

此外,從隨機函數中調用setSeed是不好的。 多個后續調用將返回相同的值。 你只想設置一次種子。

這是一個使用System.Random的簡單線程安全解決方案,如果它有任何幫助:

let random =
  let rand = System.Random()
  let locker = obj()
  fun () -> lock locker rand.Next

最好將所有m_w / m_z和相關函數放入一個類中。 像這樣:

type Random = 
    let setSeed() = ...
    let randomNormal() = ...

之后至少有兩個解決方案:使用自己的Random對象實例啟動每個線程; 或者使用ThreadLocal<Random>ThreadLocal<Random>同樣的事情 - 保證每個線程都有自己的Random類實例。

編輯:另外,使用PostAndReply方法的MailboxProcessor是在線程之間共享單個生成器的好方法。 無需擔心同步。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM