2016-04-21 12 views
1

Ich suche eine schnelle Art und Weise zu lassen viele Worker-Threads warten auf ein Ereignis fortzusetzen und blockieren den Haupt-Thread, bis alle Worker-Threads sind fertig zu lassen. Ich habe zuerst TPL oder AutoResetEvent verwendet, aber da meine Berechnung nicht so teuer ist, war der Aufwand viel zu hoch.beste Weg, um viele Arbeiter-Threads warten Mainthread und umgekehrt

Ich habe eine ziemlich interessante article zu diesem Problem gefunden und gute Ergebnisse (mit nur einem Worker-Thread) mit der letzten Synchronisationslösung (Interlocked.CompareExchange). Aber ich weiß nicht, wie ich es für ein Szenario verwenden soll, in dem viele Threads wiederholt auf ein Hauptprofil warten.

Hier ist ein Beispiel unter Verwendung von einzelnen Thread, CompareExchange und Barrier:

static void Main(string[] args) 
{ 
    int cnt = 1000000; 

    var stopwatch = new Stopwatch(); 

    stopwatch.Start(); 
    for (int i = 0; i < cnt; i++) { } 
    Console.WriteLine($"Single thread: {stopwatch.Elapsed.TotalSeconds}s"); 

    var run = true; 
    Task task; 

    stopwatch.Restart(); 
    int interlock = 0; 
    task = Task.Run(() => 
    { 
     while (run) 
     { 
      while (Interlocked.CompareExchange(ref interlock, 0, 1) != 1) { Thread.Sleep(0); } 
      interlock = 2; 
     } 
     Console.WriteLine($"CompareExchange synced: {stopwatch.Elapsed.TotalSeconds}s"); 
    }); 

    for (int i = 0; i < cnt; i++) 
    { 
     interlock = 1; 
     while (Interlocked.CompareExchange(ref interlock, 0, 2) != 2) { Thread.Sleep(0); } 
    } 
    run = false; 
    interlock = 1; 
    task.Wait(); 

    run = true; 
    var barrier = new Barrier(2); 
    stopwatch.Restart(); 
    task = Task.Run(() => 
     { 
      while (run) { barrier.SignalAndWait(); } 
      Console.WriteLine($"Barrier synced: {stopwatch.Elapsed.TotalSeconds}s"); 
     }); 

    for (int i = 0; i < cnt; i++) { barrier.SignalAndWait(); } 
    Thread.Sleep(0); 
    run = false; 
    if (barrier.ParticipantsRemaining == 1) { barrier.SignalAndWait(); } 
    task.Wait(); 

    Console.ReadKey(); 
} 

Durchschnittliche Ergebnisse (in Sekunden) sind:

Einfaden: 0,002 CompareExchange: 0,4 Barrier: 1

, 7

Wie Sie Barriers' Overhead sehen scheint arround 4-mal höher zu sein! Wenn jemand mir das CompareExchange-Szenario wieder zusammenstellen kann, um mit mehreren Worker-Threads zu arbeiten, würde dies sicherlich auch helfen!

Sicher, 1 Sekunde Aufwand für eine Million Berechnungen ist ziemlich weniger! Eigentlich interessiert es mich nur.

Edit:

System.Threading.Barrier scheint die schnellste Lösung für dieses Szenario zu sein. Für eine doppelte Sperrspar (alle Arbeiter bereit für die Arbeit, alle workes fertig) habe ich den folgenden Code für die besten Ergebnisse:

while(work) 
{ 
    while (barrier.ParticipantsRemaining > 1) { Thread.Sleep(0); } 
    //Set work package 
    barrier.SignalAndWait() 
} 
+0

Dieses Testprogramm sagt Ihnen leider nicht viel, weil die Threads keine Arbeit machen. Sie sollten ihnen eine Art von Last hinzufügen - vielleicht rufen Sie eine Methode auf, die einige Berechnungen mit Fließkomma ausführt. –

+0

Misst dieser Code nicht den fast reinen Synchronisationsaufwand? Sicher, das Hinzufügen von Berechnungen wird die Ergebnisse mehr und mehr gleich machen. – user1039407

+0

Es ist sehr schwierig Multithread-Sachen zu vergleichen, wenn Threads nicht blockieren, weil man immer den "glücklichen Pfad" festlegt. Aber sicher werden Sie nichts schneller als "Interlocked.CompareExchange()" bekommen, aber es ist irgendwie irrelevant, da Sie das nicht für mehr als 2 Threads verwenden können. –

Antwort

0

Es scheint, wie Sie ein Barrier verwenden möchten eine Reihe von Arbeitnehmern zu synchronisieren ein Hauptthema.

Hier ist ein Beispiel übersetzbar. Spielen Sie damit, achten Sie darauf, wenn Ihnen die Ausgabe sagt, dass Sie "<Return> drücken können, um den Arbeitern den Start zu signalisieren".

using System; 
using System.Diagnostics; 
using System.Threading; 
using System.Threading.Tasks; 

namespace Demo 
{ 
    static class Program 
    { 
     static void Main() 
     { 
      print("Main thread is starting the workers."); 
      int numWorkers = 10; 

      var barrier = new Barrier(numWorkers + 1); // Workers + main (controlling) thread. 

      for (int i = 0; i < numWorkers; ++i) 
      { 
       int n = i; // Prevent modified closure. 
       Task.Run(() => worker(barrier, n)); 
      } 

      while (true) 
      { 
       print("***************** Press <RETURN> to signal the workers to start"); 
       Console.ReadLine(); 

       print("Main thread is signalling all the workers to start."); 

       // This will wait for all the workers to issue their call to 
       // barrier.SignalAndWait() before it returns: 

       barrier.SignalAndWait(); 

       // At this point, all workers AND the main thread are at the same point. 
      } 
     } 

     static void worker(Barrier barrier, int workerNumber) 
     { 
      int iter = 0; 

      while (true) 
      { 
       print($"Worker {workerNumber} on iteration {iter} is waiting for barrier."); 

       // This will wait for all the other workers AND the main thread 
       // to issue their call to barrier.SignalAndWait() before it returns: 

       barrier.SignalAndWait(); 

       // At this point, all workers AND the main thread are at the same point. 

       int delay = randomDelayMilliseconds(); 
       print($"Worker {workerNumber} got barrier, now sleeping for {delay}"); 
       Thread.Sleep(delay); 

       print($"Worker {workerNumber} finished work for iteration {iter}."); 
      } 
     } 

     static void print(string message) 
     { 
      Console.WriteLine($"[{sw.ElapsedMilliseconds:00000}] {message}"); 
     } 

     static int randomDelayMilliseconds() 
     { 
      lock (rng) 
      { 
       return rng.Next(10000) + 5000; 
      } 
     } 

     static Random rng = new Random(); 
     static Stopwatch sw = Stopwatch.StartNew(); 
    } 
} 
+0

Klingt ziemlich gut! Ich versuche es und poste die Ergebnisse danach. – user1039407

+0

Schade: Ihre Lösung sah vielversprechend aus, aber die Umsetzung recht zu sein scheint langsam: Nichtgewinde: ~ 18,6 sec CompareExchange synchronisiert: ~ 19,6 sec Barrier synchronisiert: ~ 42,8 sec – user1039407

+0

@ user1039407 I vermute, dass das Verhalten anders ist - also testet man nicht dasselbe. Es gibt keine Möglichkeit, dass eine Barriere so viel Aufwand hat. Sie müßten jedoch eine kompilierbare Repro-Datei veröffentlichen, damit jeder sie überprüfen kann. –

Verwandte Themen