5

ich eine Reihe von Datenzeilen haben, und ich möchte Parallel.ForEach verwenden, wie dies einige Wert in jeder Zeile zu berechnen ...Mehrere parallele. Für jeden Anruf, MemoryBarrier?

class DataRow 
{ 
    public double A { get; internal set; } 
    public double B { get; internal set; } 
    public double C { get; internal set; } 

    public DataRow() 
    { 
     A = double.NaN; 
     B = double.NaN; 
     C = double.NaN; 
    } 
} 

class Program 
{ 
    static void ParallelForEachToyExample() 
    { 
     var rnd = new Random(); 
     var df = new List<DataRow>(); 

     for (int i = 0; i < 10000000; i++) 
     { 
      var dr = new DataRow {A = rnd.NextDouble()}; 
      df.Add(dr); 
     } 

     // Ever Needed? (I) 
     //Thread.MemoryBarrier(); 

     // Parallel For Each (II) 
     Parallel.ForEach(df, dr => 
     { 
      dr.B = 2.0*dr.A; 
     }); 

     // Ever Needed? (III) 
     //Thread.MemoryBarrier(); 

     // Parallel For Each 2 (IV) 
     Parallel.ForEach(df, dr => 
     { 
      dr.C = 2.0 * dr.B; 
     }); 
    } 
} 

(In diesem Beispiel gibt es keine Notwendigkeit zu parallelisieren und wenn es war, es könnte alles in eine Parallel.ForEach gehen, aber das soll eine vereinfachte Version von Code sein, wo es Sinn macht, es so einzurichten.

Ist es möglich, dass die Lesevorgänge hier neu geordnet werden, sodass ich eine Datenzeile mit B! = 2A oder C! = 2B?

Sagen Sie die erste Parallel.ForEach (II) weist Worker-Thread 42 an Datenzeile 0 arbeiten. Und die zweite Parallel.ForEach (IV) weist Worker-Thread 43 zu Datenzeile 0 arbeiten (sobald die erste Parallele .Für jedes Ende). Gibt es eine Chance, dass das Lesen von dr.B für Zeile 0 auf Thread 43 double.NaN zurückgibt, da es das Schreiben von Thread 42 noch nicht gesehen hat?

Und wenn ja, hilft das Einfügen einer Speicherbarriere bei III überhaupt? Würde dies die Updates von der ersten Parallel.ForEach für alle Threads erzwingen, bevor die zweite Parallel.ForEach startet?

+0

Kurz gesagt .. Ich glaube nicht, dass Sie die explizite Gedächtnis Barrieren müssen .. eine Vermutung, dass die Umsetzung Parallel wäre.ForEach hat eine Art Synchronisation zum Beenden der Schleife/vor Aufruf 'ForEach' zurück –

+0

ein besseres Bild von Ihrem aktuellen Code gegeben, könnte ich in der Lage sein, Ihnen eine bessere andere Antwort zu geben, als„Nein, darüber keine Sorge. " :) – jdphenix

+0

Vielleicht ist der Grund für die Trennung ist ein wenig klarer, wenn ich sage, daß die Berechnung in jeder Zeile der zweiten parallelen Schleife (IV) auf einem Wert ab, die erst nach der ersten Schleife bekannt sein können (II) abgeschlossen ist. Nehmen wir an, wir brauchen den Mittelwert der Werte von dr.B über alle Zeilen hinweg, bevor wir den Wert von dr.C für jede Zeile berechnen können. –

Antwort

4

Die von einer Parallel.ForEach() begonnene Arbeit wird ausgeführt, bevor sie zurückkehrt. Intern erstellt ForEach() eine Task für jede Iteration und ruft Wait() auf jedem einzeln auf. Daher müssen Sie den Zugriff zwischen ForEach()-Aufrufen nicht synchronisieren.

Sie Notwendigkeit zu tun, dass für einzelne Aufgaben mit ForEach() Überlastungen im Auge zu behalten, die Sie Schleife Zustand Zugriff erlauben, Ergebnisse von Aufgaben, etc. Zum Beispiel in diesem trivialen Beispiel aggregiert die 1 ≤ x ≤ 100 fasst, die Action weitergegeben localFinally von Parallel.For() hat besorgt über Probleme bei der Synchronisierung sein,

var total = 0; 

Parallel.For(0, 101,() => 0, // <-- localInit 
(i, state, localTotal) => { // <-- body 
    localTotal += i; 
    return localTotal; 
}, localTotal => { <-- localFinally 
    Interlocked.Add(ref total, localTotal); // Note the use of an `Interlocked` static method 
}); 

// Work of previous `For()` call is guaranteed to be done here 

Console.WriteLine(total); 

In Ihrem Beispiel ist es nicht notwendig ist, eine Speicherbarriere zwischen den ForEach() Anrufen einzufügen. Insbesondere kann die Schleife davon abhängen, dass die Ergebnisse von II abgeschlossen sind, und Parallel.ForEach() bereits für Sie eingefügt III.

Snippet aus folgenden Quellen: Parallel Framework and avoiding false sharing

+0

Danke. Wenn ich durch den Code von Parallel.ForEach ein paar Ebenen nach unten schaue, sieht es aus wie "private statische Parallel LoopResult ForWorker " macht die meiste Arbeit. Es ist ein bisschen schwer für mich zu folgen, aber es sieht so aus, als ob es einen Aufruf an "rootTask.Wait();" Das wartet darauf, dass alle Worker-Threads beendet werden, bevor Sie fortfahren. Aber obwohl mein Hauptthread darauf wartet, dass die Worker fertig sind, garantiert das nicht, dass Worker-Threads, die auf alle anderen Prozessoren verteilt sind, beim Einlesen von Werten notwendigerweise die neuesten Schreibvorgänge sehen werden, oder? –

+0

Das ist richtig, und ich werde meine Antwort bearbeiten, um vielleicht ein bisschen klarer zu sein. Tasks, die durch den * self * 'ForEach()' Aufruf erzeugt werden, müssen auf Nebenläufigkeitsprobleme achten - der übliche Punkt, um den Sie sich kümmern müssen, ist in der 'Aktion', die Sie an' localFinally' übergeben. Unterschiedliche Aufrufe von 'ForEach()' können jedoch sicher von den Ergebnissen früherer 'ForEach()' - Aufrufe sicher abhängen. – jdphenix

+0

Ich denke, meine Frage hängt damit zusammen ... http://stackoverflow.com/questions/6581848/memory-barrier-generators. Ich möchte nur sicherstellen, dass das Ende von Parallel.ForEach in einen dieser Eimer fällt. Damit hat es eine eigene MemoryBarrier (effektiv) und garantiert, dass alles vollständig geschrieben ist, bevor die nächste Parallel.ForEach startet. –

0

Seit mehr als einem Thread die gleiche Variable „Dr.B“ zugreifen, müssen Sie Ihre C# -Code stellen Sie sicher, Thread-sicher ist.

Versuchen "Verriegelungs" round jede Operation https://msdn.microsoft.com/en-us/library/c5kehkcz.aspx

z.B. unter Verwendung von

private Object thisLock1 = new Object(); 
... 
lock(thisLock1) 
{ 
    dr.C = 2.0 * dr.B; 
} 

... 
lock(thisLock1) 
{ 
    dr.B = 2.0*dr.A; 
} 

Allerdings wird dies die parallele Verarbeitung vereiteln. da jeder Thread warten muss, bis der nächste fertig ist.

Achten Sie darauf, das Potenzial pitfall mit Parallelverarbeitung zu lesen: https://msdn.microsoft.com/en-us/library/dd997403%28v=vs.110%29.aspx

+0

Im konkreten Beispiel des OP 'Parallel.ForEach mit()', 'jeder foreach()' Call Griffe bereits Synchronisation, keine parallelen Operationen durch den Aufruf gelaicht speziell sichergestellt abgeschlossen sind, bevor er zurückkehrt. – jdphenix

+0

@jdphenix - können Sie bitte eine Referenz angeben (für meine Ausbildung)? Hinweis Microsoft MSDN zeigt: Gewusst wie: Schreiben einer Parallel.ForEach-Schleife, die Thread-lokale Variablen hat https://msdn.microsoft.com/en-us/library/dd460703%28v=vs.110%29.aspx, die (finalResult) => Interlocked.Add (ref insgesamt finalResult) –

+0

Es ist richtig, zu behaupten, dass ein einzelner 'FürJeden()' braucht Thread-Sicherheit zu prüfen, und als solche 'FürJeden()' liefert Überlastungen, die Spezifikation eines Fadens erlauben Local und Finalizer wie du verlinkt hast. Soweit das 'Wait()' intern zu 'ForEach()' aufruft, musste ich über die Referenzquelle schauen, um dies zu bestätigen. – jdphenix

Verwandte Themen