1

Я использую org.apache.commons.math3.distribution.NormalDistribution в большом распространенном приложении Scala & Akka. Во время отладки я нашел sample() время от времени возвращались NaN, который распространяющаяся молча и вызвал нить, чтобы повесить в org.apache.commons.math3.ode.nonstiff.DormandPrince853IntegratorПредупреждения о безопасности нитей

Наны могут быть воспроизведены только с параллельным colelctions (не происходит в последовательном коде):

val normal = new NormalDistribution(0,0.1) 
(1 to 1000000000).par.foreach{i => 
    val r = normal.sample 
    if(r.isNaN()) throw new Exception("r = "+r) 
} 

Очевидно, что перемещение val normal внутри foreach решает проблему в этом случае.

Я просмотрел docs, но не вижу ничего предупреждающего меня о таких проблемах. Неужели я не понял более фундаментальную концепцию безопасности потоков? Излишне говорить, что я сейчас проверяю NaN.

ответ

3

К digging through sources вы можете обнаружить, что этот конструктор использует Well19937c случайный генератор, который не выглядит нитевидным само по себе с первого взгляда.

Вы можете сделать его потокобезопасным, явно установив генератор чисел в SynchronizedRandomGenerator, который обертывает любой другой генератор случайных чисел (например, Well19937c или Mersenne Twister). Обратите внимание, что, синхронизируя доступ к генератору случайных чисел с SynchronizedRandomGenerator, вы потеряете все потенциальные преимущества производительности, а «параллельная» версия будет, вероятно, медленнее, чем последовательная, из-за синхронизации. С другой стороны, повторная инициализация случайного распределения на каждой итерации параллельно, вероятно, повторно запустит PRNG несколько раз с аналогичными значениями в зависимости от текущего времени, поэтому ваши результаты будут искажены.

Очень общее правило большого пальца (и если я ошибаюсь здесь, пожалуйста, исправьте меня), это то, что в 99% случаев, если явно не указано иначе, вы, вероятно, должны придерживаться последовательного выполнения при выполнении всего, что зависит от генерация случайных чисел, так как обычно PRNG будут сохранять состояние, которое может быть повреждено при вызове из нескольких потоков. И если вы не сделаете дорогостоящие вычисления впоследствии, синхронизация (в случае потокобезопасных состояний PRNG) будет узким местом.

+0

Обратите внимание, что старый добрый 'java.util.Random' является и поточным, и незамкнутым ... –

+0

@MichaelBorgwardt Правда, и хороший улов! Кто-то, кто знает apache.commons.math, может направить человека, задающего вопрос о том, как вложить его в случайные распределения, если это возможно. Я действительно ничего не знаю об этой библиотеке, поэтому я не могу помочь там ... –

1

Возможно, это связано с тем, что вы используете объект, не являющийся потокобезопасным, в многопоточной среде (вы вызываете образец метода дважды или более одновременно). Вам необходимо использовать другой поточно-безопасный генератор или экземпляр NormalDistribution для каждого потока или синхронизировать доступ к одному экземпляру (возможно, потеряв преимущество при выполнении парного исполнения).

1

Попробуйте использовать другой поточно-безопасный генератор или экземпляр NormalDistribution для каждого потока или синхронизировать доступ к одному экземпляру. Потому что я думаю, что вы используете объект non-threadsafe в многопоточной среде.

2

Средней почвой было бы создать normal как нить локальную, perhap с помощью Twitter's Local implementation.

Это поможет, если метод normal.sample был особенно дорогим. Вы также можете быть уверены, что одновременно не будут выполняться две параллельные операции в одном и том же потоке :)

Смежные вопросы