2016-05-02 7 views
0

なぜforeach関数でprintln文を追加すると結果が変わるのですか?競合状態ザッツスカラ並列コレクションforeach異なる結果を返す

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

一覧は並列コレクションのforeachであることから、並行して実行し、非同期変数sumを変異させます。

なぜ、最初のforeachで正しい結果を印刷しているのですか?ブロック内にprintlnがあるため、それを削除すると、データ競合が発生します。

へのprintlnデリゲートには、​​ブロックが内部にあります。

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

Btwは、合計を並列化するための良い方法ではありません。

+0

返信ありがとうございます。 2番目のforeach(変数sumがどのスレッドで更新されたかを追跡する)のように突然変異をトレースする方法はありますか?それが私の本来の目標でした。 –

+0

@StanleyZhang申し訳ありませんが、私は良い答えがありません。 –

0

Scalaは、特にこのようなことが起こるため、可変性に対する不変性を奨励します。変更可能な変数valがある場合、変更を認識していない別のスレッドによって既に読み取られている可能性があります。 すべてのスレッドが * 3のスレッドが0として値和を読んで、そのため、 * 1スレッドは、34であることを起こるおり、sum + xを書き込み関数を呼び出すこと:これは、以下の現象が発生する原因となるように並列に合計を行う

並列、加算は任意の順序で行われます * 1さらにスレッドは * 2のスレッドが17を読み取る前に値0を読み込んでいるため、と計算され、0 + 17(*が17だったとします)と計算します* *最初の3つのスレッドの最後には、0が読み込まれたので、0 + 9が書き込まれます。

TLDRでは、複数のスレッドが他のスレッドが書き込みを行っている間に読み込み、互いの変更を上書きするため、メモリへの読み取りと書き込みが同期しなくなります。

解決策は、これを順番に実行する方法を見つけることです。パラレル化を非破壊的に活用することです。合計のような関数はfoldLeft、例えば、順番に、または常に新しい値を生成する方法で行うべきである:

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

それとも、和のサブセットを作成しparalelでそれらを追加し、追加funcitonを書くことができますそれらのすべてを順番に一緒に:

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} 
関連する問題