Scala并行集合foreach返回不同的结果
为什么在foreach函数中添加println语句会改变结果Scala并行集合foreach返回不同的结果,scala,parallel-processing,functional-programming,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 (s
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)
这是一个竞争条件,因为List是一个并行集合,foreach将并行运行,并对未同步的变量sum进行变异 现在为什么要在第一个foreach中打印正确的结果?由于块内部存在
println
,如果将其删除,您将遇到数据竞争
println委托给PrintStream.println
,它内部有一个synchronized
块
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
顺便说一句,这不是并行求和的好方法。这是一个竞争条件,因为列表是一个并行集合,每个集合将并行运行,并对未同步的变量求和进行变异 现在为什么要在第一个foreach中打印正确的结果?由于块内部存在
println
,如果将其删除,您将遇到数据竞争
println委托给PrintStream.println
,它内部有一个synchronized
块
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
顺便说一句,这不是并行求和的好方法。Scala鼓励不变性而不是易变性,特别是因为这样的事情会发生。当您有可以更改的
val
变量时,您可以通过更改内存中的值来创建竞争条件,这些值可能尚未被另一个未实现更改的线程读取
这样并行求和会导致以下情况:
要调用该函数的所有线程
*3个线程将值总和读取为0,
*1线程写入sum+x
,它恰好是34
,因为它是并行的,加法可以按任意顺序进行
*又有一个线程写入sum+x
,它将其计算为0+17
(假设*为17),因为它在写入内存之前读取了值0
*2个线程读取17
*前三个线程中的最后一个线程写入0+9
,因为它读取了0
TLDR中,对内存的读取和写入不同步,因为几个线程可能在其他线程写入时读取,并覆盖其他线程的更改
解决方案是找到一种按顺序进行的方法,或者以非破坏性的方式利用并行化。像sum这样的函数应该按顺序执行,或者以总是生成新值的方式执行,例如foldLeft:
Seq(1, 2, 3, 4).foldLeft(0){case (sum, newVal) => sum + newVal}
或者您可以编写一个函数,创建和的子集,并行相加,然后按顺序将所有这些相加:
Seq(1, 2, 3, 4, 5, 6, 7, 8).grouped(2).toSeq.par.map {
pair =>
pair.foldLeft(0){case (sum, newVal) => sum + newVal}
}.seq.foldLeft(0){case (sum, newVal) => sum + newVal}
Scala鼓励不变性而不是易变性,特别是因为这样的事情会发生。当您有可以更改的
val
变量时,您可以通过更改内存中的值来创建竞争条件,这些值可能尚未被另一个未实现更改的线程读取
这样并行求和会导致以下情况:
要调用该函数的所有线程
*3个线程将值总和读取为0,
*1线程写入sum+x
,它恰好是34
,因为它是并行的,加法可以按任意顺序进行
*又有一个线程写入sum+x
,它将其计算为0+17
(假设*为17),因为它在写入内存之前读取了值0
*2个线程读取17
*前三个线程中的最后一个线程写入0+9
,因为它读取了0
TLDR中,对内存的读取和写入不同步,因为几个线程可能在其他线程写入时读取,并覆盖其他线程的更改
解决方案是找到一种按顺序进行的方法,或者以非破坏性的方式利用并行化。像sum这样的函数应该按顺序执行,或者以总是生成新值的方式执行,例如foldLeft:
Seq(1, 2, 3, 4).foldLeft(0){case (sum, newVal) => sum + newVal}
或者您可以编写一个函数,创建和的子集,并行相加,然后按顺序将所有这些相加:
Seq(1, 2, 3, 4, 5, 6, 7, 8).grouped(2).toSeq.par.map {
pair =>
pair.foldLeft(0){case (sum, newVal) => sum + newVal}
}.seq.foldLeft(0){case (sum, newVal) => sum + newVal}
谢谢你的回复。有没有办法像第二个foreach那样跟踪变异(跟踪变量sum在哪个线程中更新的时间)?这是我最初的目标。谢谢你的回复。有没有办法像第二个foreach那样跟踪变异(跟踪变量sum在哪个线程中更新的时间)?这是我最初的目标。