我在大型分布式Scala和Akka应用程序中使用org.apache.commons.math3.distribution.NormalDistribution。在调试过程中,我发现sample()偶尔返回NaN,它以静默方式传播并导致线程挂在org.apache.commons.math3.ode.nonstiff.DormandPrince853Integrator

NaN可以简单地通过并行排序来复制(在顺序代码中不会发生):

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。

最佳答案

通过digging through sources,您可以发现此构造函数使用了Well19937c随机生成器,乍一看,它本身并不具有线程安全性。

您可以通过将数字生成器显式设置为SynchronizedRandomGenerator来包装它,该数字生成器将包装任何其他随机数字生成器(如Well19937cMersenne Twister)。请注意,通过使用SynchronizedRandomGenerator同步对随机数生成器的访问,您将失去所有潜在的性能优势,并且由于同步,“并行”版本可能会比顺序版本慢。另一方面,在并行执行的每个迭代中重新初始化随机分布可能会根据当前时间以相似的值多次重新种子PRNG,因此结果将出现偏差。

一个非常普遍的经验法则(如果我错了,请纠正我)是在99%的时间内,除非另有明确说明,否则在执行任何依赖于随机数生成的操作时,您可能应该坚持顺序执行,通常,PRNG将存储从多个线程调用它们时可能损坏的状态。并且除非以后进行大量的计算,否则同步(在线程安全的有状态PRNG情况下)将成为瓶颈。

07-28 03:38
查看更多