Является ли генератор случайных чисел в Haskell потокобезопасным?
Является ли тот же "генератор глобальных случайных чисел" общим для всех потоков или каждый поток получает свой собственный?
Если общий доступ, как я могу обеспечить безопасность потоков? Подход с использованием getStdGen и setStdGen, описанный в главе "Monads" в Real World Haskell не выглядит безопасным.
Если каждый поток имеет независимый генератор, будут ли генераторы для двух потоков, начинающиеся с быстрой последовательностью, иметь разные семена? (Они не будут, например, если семя - это время в секундах, но миллисекунды могут быть в порядке. Я не вижу, как получить время с разрешением в миллисекундах от Data.Time.)
Ответы
Ответ 1
Существует функция с именем newStdGen
, которая дает один новый std. gen каждый раз, когда он вызывал. Его реализация использует atomicModifyIORef
и, следовательно, является потокобезопасной.
newStdGen
лучше, чем get/setStdGen, не только с точки зрения безопасности потоков, но и защищает вас от потенциальных однопоточных ошибок, таких как: let rnd = (fst . randomR (1,5)) <$> getStdGen in (==) <$> rnd <*> rnd
.
Кроме того, если вы думаете о семантике newStdGen
vs getStdGen
/setStdGen
, первые могут быть очень простыми: вы просто получаете новый std. gen в случайном состоянии, выбранном недетерминистически. С другой стороны, с помощью пары get/set вы не можете абстрагировать глобальное состояние программы, что плохо по нескольким причинам.
Ответ 2
Я предлагаю вам использовать getStdGen
только один раз (в основном потоке), а затем использовать функцию split
для генерации новых генераторов. Я бы сделал это следующим образом:
Сделайте MVar
, который содержит генератор. Всякий раз, когда поток нуждается в новом генераторе, он берет текущее значение из MVar
, вызывает split
и возвращает новый генератор. Из-за функциональности MVar
это должно быть потокобезопасным.
Ответ 3
Сам по себе getStdGen
и setStdGen
не являются потокобезопасными в определенном смысле. Предположим, что оба потока выполняют это действие:
do ...
g <- getStdGen
(v, g') <- someRandOperation g
setStdGen g'
Возможно, что потоки будут запускать строку g <- getStdGen
до того, как другой поток достигнет setStdGen
, поэтому оба они могут получить тот же самый генератор. (Я не прав?)
Если они оба захватят одну и ту же версию генератора и используют ее в той же функции, они получат одинаковый "случайный" результат. Поэтому вам нужно быть немного более осторожным, когда речь идет о генерации случайных чисел и многопоточности. Существует много решений; тот, который приходит на ум, состоит в том, чтобы иметь отдельный поток генератора случайных чисел, который создает поток случайных чисел, которые другие потоки могли бы потреблять поточно-безопасным способом. Полагая генератор в MVar, как предлагает FUZxxl, вероятно, является самым простым и самым простым решением.
Конечно, я бы посоветовал вам проверить ваш код и убедиться, что необходимо генерировать случайные числа более чем в одном потоке.
Ответ 4
Вы можете использовать split
как в ответе FUZxxl. Однако, вместо того, чтобы использовать MVar, всякий раз, когда вы вызываете forkIO
, просто выполняйте действие IO для разветвленного потока над одним из результирующих генераторов и оставите другое с исходным потоком. Таким образом, каждый поток имеет свой собственный генератор.
Как сказал Дэн Бертон, проверьте свой код и посмотрите, действительно ли вам нужен RNG в нескольких потоках.