2010-02-19 16 views
5

Lassen Sie uns sagen, ich habe zwei Arrays:Zugriff auf Array von mehreren Threads

int[] array1 = new int[2000000]; 
int[] array2 = new int[2000000]; 

ich einige Werte in den Arrays bleiben und wollen dann den Inhalt von array2 hinzuzufügen wie so Array1:

for(int i = 0; i < 2000000; ++i) array1[i] += array2[i]; 

Nehmen wir an, ich möchte die Verarbeitung auf einem Multi-Prozessor-Rechner beschleunigen. Anstatt nur eine Schleife wie oben zu erstellen, erstelle ich zwei Threads. Eine davon habe ich die ersten 1000000 Elemente im Array verarbeiten, die andere habe ich die letzten 1000000 Elemente im Array verarbeiten. Mein Hauptthread wartet auf diese beiden Threads, um sie darüber zu informieren, dass sie fertig sind, und fährt dann fort, die Werte von array1 für alle Arten wichtiger Dinge zu verwenden. (Beachten Sie, dass die beiden Worker-Threads möglicherweise nicht beendet werden und wiederverwendet werden können, aber der Hauptthread wird erst fortgesetzt, wenn beide dies gemeldet haben.)

Meine Frage lautet also: Wie kann ich sicher sein? Der Haupt-Thread wird die Änderungen sehen, die die beiden Worker-Threads an das Array vorgenommen haben. Kann ich darauf vertrauen, dass dies geschieht, oder muss ich eine spezielle Prozedur durchlaufen, um sicherzustellen, dass die Worker-Threads ihre Schreibvorgänge in das Array leeren und der Hauptthread seine zwischengespeicherten Array-Werte verwirft?

Antwort

2

Sie benötigen eine Speicherbarriere, um sicherzustellen, dass die auf das Array des Arbeitsthread zu dem Haupt-Thread in der Reihenfolge, die Sie erwarten sichtbar schreibt.

Ob Sie eine explizite Speicherbarriere benötigen, hängt davon ab, wie Sie den Hauptthread benachrichtigen. Das Warten auf die meisten Synchronisationsgrundelemente, z. B. Ereignisse, stellt eine implizite Barriere dar, so dass keine Änderungen von Ihrer Seite erforderlich sind. Das Abfragen einer globalen Variablen bietet keine Barriere.

Wenn Sie eine explizite Barriere benötigen, verwenden Sie Thread.MemoryBarrie r.

+2

Vorausgesetzt, dass er plant, auf den Haupt-Thread (aus der Beschreibung) zu blockieren, sollte die Speicherbarriere unnötig sein. –

+0

Er hat nicht explizit gesagt, wie er gewartet hat (es könnte eine Weile dauern (! Fertig) {}). Jede vernünftige Art zu warten erfordert keine Barriere. – Michael

+0

Ich verwende ManualResetEvent-Objekte, damit der Hauptthread Worker-Threads signalisiert und umgekehrt. Ich war irgendwie neugierig auf die Funktionalität von MemoryBarrier, da ich schon daran gearbeitet habe. Die Dokumente von MS sagen sehr wenig darüber aus und sagen im Grunde genommen, dass es die Neuanordnung von Anweisungen verhindert. Ich hatte den Eindruck, dass es den aktuellen Thread Cache geleert/geleert hat, aber nie sicher wusste. (Wenn dies der Fall ist, könnten die Worker auch ohne echte Thread-Synchronisation MemoryBarrier aufrufen und dann, wenn der Hauptthread später SpeicherBarrier genannt wird, würde es die Änderungen am Array sehen, nicht wahr?) – nonoitall

0

Sie werden wahrscheinlich in Ordnung sein, wenn Sie Callbacks verwenden, wenn sie fertig sind, die Arrays zu modifizieren, aber wenn es eine Frage mit einem Lock gibt, wird sichergestellt, dass die anderen Threads die Arrays losgelassen haben.

lock (array1) 
{ 
} 

http://msdn.microsoft.com/en-us/library/c5kehkcz(VS.71).aspx

+1

Das Sperren des gesamten Arrays für die Dauer der Operation eliminiert die Fähigkeit der Threads, parallel zu arbeiten. – Michael

+1

Es ist auch nicht notwendig. –

+0

Eine Sperre stellt sicher, dass kein anderes Thread das Array gesperrt hat, wenn ein bestimmter Thread es eingibt - es stellt nicht sicher, dass andere Threads ihre Sperre aufgeben. – JoshJordan

0

Solange Sie keine Kopie des Arrays im Hauptthread erstellt haben, denke ich nicht, dass Sie etwas tun müssen. Warte einfach, bis die Worker-Threads fertig sind.

2

Wie kann ich sicher sein, dass der Hauptthread die Änderungen sieht, die die beiden Worker-Threads an das Array vorgenommen haben? Kann ich darauf vertrauen, dass dies geschieht, oder muss ich eine spezielle Prozedur durchlaufen, um sicherzustellen, dass die Worker-Threads ihre Schreibvorgänge in das Array leeren und der Hauptthread seine zwischengespeicherten Array-Werte verwirft?

Sie brauchen hier keine spezielle Behandlung - Sie werden immer die gleichen Objekte im Speicher bearbeiten.

Da auch jeder Thread auf einem separaten Teil des Arrays funktioniert, ist keine Verriegelung erforderlich.

Wenn Sie jedoch nur eine einfache Ergänzung machen, kann der Overhead von Threading und Synchronisierung zurück zum Hauptthread ~ die Vorteile überwiegen ... Wenn Sie dies tun, stellen Sie sicher, dass ein Profil erstellt wird Nettogewinn.

+0

Ja - die tatsächliche Operation ist ein bisschen komplexer als die Addition (wurde nur als Beispiel verwendet), aber es ist das gleiche, dass das n-te Element von array1 basierend auf sich selbst und dem n-ten Element von array2 aktualisiert wird. Die Profilerstellung auf einem Paar Arrays von ordentlicher Größe zeigt fast die doppelte Leistung, wenn man von einem Thread zu zwei auf einem Dual-Core-Rechner geht, also bin ich mir ziemlich sicher, dass es das wert ist. Ich möchte nur sicher sein, dass ich es richtig mache. :-) – nonoitall

+0

@nonoitall: Solange Sie immer das n-te Element in einem einzigen Thread zu einem einzigen Zeitpunkt verwenden, sollte es kein Problem sein. Freut mich zu hören, dass es sich lohnt! –

1

Wenn Sie den Indexbereich in nicht überlappende Bereiche wie vorgeschlagen aufteilen, vorausgesetzt, das Array wird im gemeinsam genutzten Speicher erstellt (d. H. Nicht von jedem Thread), sind keine Sperren erforderlich.

5

Wenn Sie Glück haben und haben die Möglichkeit, 4.0 .NET zu verwenden, dann können Sie einfach schreiben:

Parallel.For(0, 2000000, i => { array1[i] += array2[i]; }); 

Sie benötigen keine explizite Sperren oder Synchronisation, weil:

  • Jede Task (Ausführung des Schleifenkörpers for) wirkt sich auf den disjunkten Teil des Arrays aus.
  • Parallel.For wartet, bis alle Tasks beendet sind, bevor es zurückkehrt, sodass eine implizite Speicherbarriere entsteht.
+0

Das ist großartig! Ich wusste nicht, dass sie .NET 4.0 hinzugefügt haben. Wird es unter Mono dennoch unterstützt? – nonoitall

+0

Eine Qucik-Suche zeigt, dass es einige Versuche gibt, paralleles Zeug in .NET 4.0 unter Mono zu unterstützen: http://tirania.org/blog/archive/2008/Jul-26-1.html. Ich bin mir nicht sicher, was der aktuelle Status ist, obwohl ... –

+1

Ich habe es ausprobiert, aber es scheint Parallel.Für nur etwas schneller als mit einem einzigen Thread.Da meine "Addition" -Operation ziemlich schnell ist, nehme ich an, dass die unscheinbare Geschwindigkeit von dem versteckten Overhead des Aufrufs eines Delegaten für jede Schleifeniteration herrührt. Schade, dass die CLR den Delegaten zur Laufzeit nicht einbinden kann. Ich nehme an, ich könnte die Schleife abwickeln, um sie zu beschleunigen, aber ich denke, ich werde mich vorerst an die manuelle Methode halten. – nonoitall

Verwandte Themen