为什么在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)
答案 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)
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}