这是一个正确的线程安全的随机包装器吗?

时间:2011-10-17 04:45:07

标签: random f# thread-safety

我对线程和并发性缺乏经验;为了解决这个问题,我目前正在努力实现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确保对于一个实例,每个线程接收自己的Random实例,其自己的随机种子由一个公共的静态Random提供。这样,即使及时创建了类的多个实例,它们也会收到自己的种子,避免了“重复”随机序列的问题。该锁强制执行没有两个线程将获得相同的种子。

这看起来是否正确?有明显的问题吗?

4 个答案:

答案 0 :(得分: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 :(得分:3)

这感觉不对劲。为什么不使用单例(只创建一个Random实例,并将其锁定)?

如果真正的随机性是一个问题,那么可能会看到RNGCryptoServiceProvider,它是线程安全的。

答案 2 :(得分:1)

除非存在性能瓶颈,否则我认为

let rand = new Random()

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

会更容易理解,但我认为你的方法应该没问题。

答案 3 :(得分:0)

如果你真的想要采用OO方法,那么你的代码可能没问题(我不会说'它'很好,因为我不太聪明,不能理解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生成“可能性”类型对象,然后将它们分发到可以并行处理它们的各种线程。 这里重要的是我们没有在核心类型中引入锁等,即概率,并且消费者有责任如何跨线程分发它。