Scala并行收集foreach返回不同的结果

时间:2016-05-02 23:08:37

标签: scala parallel-processing functional-programming

为什么在foreach函数中添加println语句会改变结果?

var sum = 0
val list = (1 to 100).toList.par
 list.tasksupport = 
   new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(4))
 list.foreach ((x: Int) => { println (x,sum); sum += x})
 //5050
 println (sum)
 sum = 0
 list.foreach ((x: Int) => sum += x)
 //results vary
 println (sum)

2 个答案:

答案 0 :(得分:2)

这是一个竞争条件,因为List是一个并行Collection foreach将并行运行并改变未同步的变量sum。

现在为什么它会在第一个foreach中打印出正确的结果?由于块内有println,因此将其删除,您将遇到数据竞争。

println委托给PrintStream.println里面有synchronized块。

 public void println(Object x) {
    String s = String.valueOf(x);
    synchronized (this) {
        print(s);
        newLine();
    }
}

顺便说一句,这不是和平化总和的好方法。

答案 1 :(得分:0)

Scala鼓励变异性的可变性,特别是因为这样的事情发生了。如果您有5个变量,可以更改,您可以创建竞争条件,因为内存中的值已更改,或者可能尚未被另一个未实现更改的线程读取。

像这样并行执行求和会导致以下情况发生: 所有线程都要调用该函数 * 3个线程将值sum读为0, * 1个线程写val,恰好是sum + x,因为它是并行的,加法以任何顺序发生 *另外1个线程写34,它计算为sum + x(假设它是17),因为它在写入内存之前读取值0 *另外2个线程读取17 *前三个线程中的最后一个写入0 + 17,因为它已读为0。

TLDR,对内存的读写不同步,因为有些线程可能会在其他线程正在写入时读取,并且会覆盖彼此的更改。

解决方案是找到按顺序执行此操作的方法,或以非破坏性方式利用并行化。像sum这样的函数应该按顺序完成,或者以总是生成新值的方式完成,例如foldLeft:

0 + 9

或者您可以编写一个函数来创建sums的子集,将它们添加到paralel中,然后按顺序将所有这些添加到一起:

Seq(1, 2, 3, 4).foldLeft(0){case (sum, newVal) => sum + newVal}