2011-10-17 70 views
1

我對線程和併發性相當缺乏經驗;爲了彌補這一點,我目前正在爲在F#中實現隨機搜索算法而努力。我根據現有C#示例的想法編寫了一個System.Random類的包裝器,但由於我不確定我怎麼會開始單元測試這個錯誤行爲,所以我想聽聽更多有經驗的人想說什麼,如果有明顯的缺陷或我的代碼的改進,無論是由於F#的語法或線程誤區:這是一個適當的線程安全的隨機包裝?

open System 
open System.Threading 

type Probability() = 

    static let seedGenerator = new Random() 

    let localGenerator = 
     new ThreadLocal<Random>(
     fun _ -> 
      lock seedGenerator (
       fun _ -> 
        let seed = seedGenerator.Next() 
        new Random(seed))) 

    member this.Draw() = 
     localGenerator.Value.NextDouble() 

我的這是什麼一樣的理解:ThreadLocal的確保了一個實例,每個線程接收它自己的實例一個隨機,隨機種子由一個共同的靜態隨機提供。這樣,即使該類的多個實例在時間上接近創建,它們也會接收到它們自己的種子,從而避免了「重複」隨機序列的問題。該鎖強制兩個線程不會獲得相同的種子。

這看起來正確嗎?有明顯的問題嗎?

回答

5

我覺得你的做法是相當合理的 - 使用ThreadLocal給您安全訪問Random和使用隨機數發生器提供種子意味着你會得到隨機值,即使你在從多個線程訪問類似的時間。它在密碼學意義上可能不是隨機的,但對於大多數其他應用程序應該沒問題。

至於測試,這是非常棘手的。如果Random中斷,它將始終返回0,但這僅僅是經驗性的經驗,很難說您需要多久保持不安全地訪問它。我建議的最好的事情是實現一些簡單的隨機性測試(一些simple ones are on WikiPedia),並從一個循環中的多個線程訪問你的類型 - 雖然這仍然是一個很糟糕的測試,因爲它可能不會每次都失敗。您可以使用type來封裝此行爲。它也可以寫成一個函數:

open System 
open System.Threading 

module Probability = 

    let Draw = 
    // Create master seed generator and thread local value 
    let seedGenerator = new Random() 
    let localGenerator = new ThreadLocal<Random>(fun _ -> 
     lock seedGenerator (fun _ -> 
     let seed = seedGenerator.Next() 
     new Random(seed))) 
    // Return function that uses thread local random generator 
    fun() -> 
     localGenerator.Value.NextDouble() 
1

除非有性能瓶頸,我認爲像

let rand = new Random() 

let rnext() = 
    lock rand (
     fun() -> 
      rand.next()) 

會比較容易理解,但我覺得你的方法應該是罰款。

+0

像這樣的代碼的問題是,如果您快速連續生成幾個對象。因爲他們都會有相同的種子。還是你的意思是那些是全局變量? – svick

+0

@svick - 點是爲什麼生成一堆隨機 - 這只是一個隨機的,你可以從多個線程訪問。這是非常簡單的,除非你需要一個可笑的數字的隨機數,它應該沒問題。如果rands是瓶頸,那麼最好用不同的發生器來獲得速度 –

+0

好吧,昂貴的位是每個隨機數的鎖。 –

0

如果你真的想用面向對象的方法,那麼你的代碼可能會很好(我不會說'這很好,因爲我不太聰明,以瞭解OO :))。但如果你想要去的功能性的方式它會像下面的那樣簡單:

type Probability = { Draw : unit -> int } 

let probabilityGenerator (n:int) = 
    let rnd = new Random() 
    Seq.init n (fun _ -> new Random(rnd.Next())) 
    |> Seq.map (fun r -> { Draw = fun() -> r.Next() }) 
    |> Seq.toList 

在這裏,你可以使用函數probabilityGenerator產生不亞於「Porbability」類型的對象,然後將它們分發到各個線程它們可以並行工作。 這裏最重要的是我們並沒有在覈心類型中引入鎖等,例如概率,它成爲消費者如何跨線程分配它的責任。

+0

我會使用'Seq.toArray'而不是'Seq.toList',以便調用者可以使用數組索引作爲線程ID。 – ildjarn

+0

該函數爲同一個線程(調用線程)生成n個項目 – Ankur

+0

是的,但是對於從多個線程的只讀訪問來說,數組是安全的,所以這個解決方案可以同樣用作單線程或多線程解決方案。 – ildjarn

3

這感覺不對頭。爲什麼不使用單例(只創建一個隨機實例並鎖定它)?

如果真正的隨機性是一個問題,那麼可能會看到RNGCryptoServiceProvider,它是線程安全的。

+0

我總是傾向於使用RNGCryptoServiceProvider進行更隨機的分配。 – 7sharp9

+0

試圖避免的問題是有兩個獨立的Random實例具有相同的種子。 –

相關問題