2016-05-02 2 views
0

Warum durch Hinzufügen einer println-Anweisung in der foreach-Funktion ändert sich die Ergebnisse?Scala parallele Sammlung foreach Rückgabe unterschiedliche Ergebnisse

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) 

Antwort

2

Das ist eine Race-Bedingung, da Liste eine parallele Sammlung foreach ist parallel laufen und die unsynchronisierte variable Summe mutieren.

Jetzt, warum es das richtige Ergebnis in der ersten foreach druckt? Wegen println innerhalb des Blocks, entfernen Sie es und Sie werden Datenrennen begegnen.

drucken Vertreter zu PrintStream.println, die einen synchronized Block enthält.

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

Btw, das ist kein guter Weg für die Parallelisierung der Summe.

+0

Danke für die Antwort. Gibt es eine Möglichkeit, die Mutation als zweite foreach zu verfolgen (Tracking, wenn die Variablensumme in welchem ​​Thread aktualisiert wird)? Das war mein ursprüngliches Ziel. –

+0

@StanleyZhang Ich habe keine gute Antwort dafür, sorry. –

0

Scala fördert die Unveränderbarkeit über die Veränderbarkeit, insbesondere weil so etwas passiert. Wenn Sie val Variablen haben, die geändert werden können, können Sie Race Conditions erstellen, da sich die Werte im Speicher ändern und nicht bereits von einem anderen Thread gelesen wurden, der die Änderung nicht erkennt.

tun Summe parallel wie dies bewirkt, dass die folgende geschehen: Alle Threads die Funktion * 3 Fäden die Wertsumme gelesen zu nennen ist als 0, * 1 Faden sum + x schreibt, die 34 sein geschieht, weil ihre parallel erfolgt die Addition in beliebiger Reihenfolge * 1 mehr Thread schreibt sum + x, die es als 0 + 17 berechnet (vorausgesetzt * es war 17), weil es den Wert 0 gelesen, bevor es in den Speicher geschrieben wurde * 2 weitere Themen lesen 17 * the Letzter der ersten drei Threads schreibt 0 + 9, weil es 0 gelesen hatte.

TLDR, die Lese- und Schreibvorgänge im Speicher werden nicht synchronisiert, da mehrere Threads gelesen werden können, während andere schreiben, und andere Änderungen überschreiben.

Die Lösung besteht darin, einen Weg zu finden, dies sequenziell zu tun oder die Parallelisierung auf eine nicht-destruktive Weise zu nutzen. Funktionen wie Summe sollten in der Reihenfolge, oder in einer Weise geschehen, die immer wieder neue Werte generieren, zum Beispiel foldLeft:

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

Oder Sie könnten eine funciton schreiben, die Teilmengen von Summen erzeugt, fügt sie in paralel, und fügt dann hinzu alle diese zusammen in Reihenfolge:

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}