Это правильный потокобезопасный случайный упаковщик? - PullRequest
1 голос
/ 17 октября 2011

Я довольно неопытен с потоками и параллелизмом; Чтобы исправить это, я в настоящее время работаю ради забавы по реализации алгоритма случайного поиска в F #. Я написал обертку вокруг класса System.Random, следуя идеям из существующих примеров на C #, но поскольку я не уверен, как бы я даже начал проводить модульное тестирование этого на предмет некорректного поведения, я хотел бы услышать, что скажут более опытные умы и если в моем коде есть явные недостатки или улучшения, либо из-за синтаксиса 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 ]

5 голосов
/ 17 октября 2011

Я думаю, что ваш подход довольно разумный - использование ThreadLocal дает вам безопасный доступ к Random, а использование master генератора случайных чисел для получения семян означает, что вы получите случайные значения, даже еслиВы получаете доступ к нему из нескольких потоков одновременно.Это может быть не случайно в криптографическом смысле, но должно подойти для большинства других приложений.

Что касается тестирования, это довольно сложно.Если Random сломается, он будет постоянно возвращать 0, но это просто эмпирический опыт, и трудно сказать, как долго вам нужно продолжать небезопасный доступ к нему.Лучшее, что я могу предложить, - это реализовать несколько простых тестов на случайность (некоторые простых тестов на 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()
3 голосов
/ 17 октября 2011

Это кажется неправильным. Почему бы просто не использовать синглтон (только когда-либо создать один случайный экземпляр и заблокировать его)?

Если реальная случайность вызывает беспокойство, то, возможно, обратитесь к RNGCryptoServiceProvider, который является потокобезопасным.

1 голос
/ 17 октября 2011

Если нет проблем с производительностью, я думаю, что-то вроде

let rand = new Random()

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

будет легче понять, но я думаю, что ваш метод должен быть в порядке.

0 голосов
/ 17 октября 2011

Если вы действительно хотите использовать ОО-подход, тогда ваш код может быть в порядке (я не скажу «все хорошо», поскольку я не слишком умен, чтобы понимать ОО :)).Но в случае, если вы хотите пойти по функциональному пути, это будет так просто, как что-то вроде:

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

Здесь вы можете использовать функцию вероятностьGenerator, чтобы сгенерировать столько же, сколько и объект типа «Porbability», а затем распределить их поразличные потоки, которые могут работать на них параллельно.Здесь важно то, что мы не вводим блокировку и т. Д. В тип ядра, то есть в вероятность, и потребители несут ответственность за то, как они хотят распределить ее по потокам.

...