2014-10-29 2 views
6

Konsolenanwendung hat 3 Threads: Main, T1, T2. Ziel ist es, ‚Signal‘ sowohl T1, T2 (und lassen Sie sie einige Arbeit tun) aus dem Haupt-Thread in der niedrigsten Latenzzeit wie möglich (us)Kommunikation mit niedriger Latenz zwischen Threads im selben Prozess

HINWEIS:

  • bitte ignorieren Bammel , GC usw. (ich kann damit umgehen)
  • ElapsedLogger.WriteLine Anrufkosten unter 50 ns (Nano sec)

einen Blick auf den Code unten haben:

Probe 1

class Program 
{ 
    private static string msg = string.Empty; 
    private static readonly CountdownEvent Countdown = new CountdownEvent(1); 

    static void Main(string[] args) 
    { 
     while (true) 
     { 
      Countdown.Reset(1); 
      var t1 = new Thread(Dowork) { Priority = ThreadPriority.Highest }; 
      var t2 = new Thread(Dowork) { Priority = ThreadPriority.Highest }; 
      t1.Start(); 
      t2.Start(); 

      Console.WriteLine("Type message and press [enter] to start"); 
      msg = Console.ReadLine(); 

      ElapsedLogger.WriteLine("Kick off!"); 
      Countdown.Signal(); 

      Thread.Sleep(250); 
      ElapsedLogger.FlushToConsole(); 
     } 
    } 
    private static void Dowork() 
    { 
     string t = Thread.CurrentThread.ManagedThreadId.ToString(); 
     ElapsedLogger.WriteLine("{0} - Waiting...", t); 

     Countdown.Wait(); 

     ElapsedLogger.WriteLine("{0} - Message received: {1}", t, msg); 
    } 
} 

Output:

Type message and press [enter] to start 
test3 
20141028 12:03:24.230647|5 - Waiting... 
20141028 12:03:24.230851|6 - Waiting... 
20141028 12:03:30.640351|Kick off! 
20141028 12:03:30.640392|5 - Message received: test3 
20141028 12:03:30.640394|6 - Message received: test3 

Type message and press [enter] to start 
test4 
20141028 12:03:30.891853|7 - Waiting... 
20141028 12:03:30.892072|8 - Waiting... 
20141028 12:03:42.024499|Kick off! 
20141028 12:03:42.024538|7 - Message received: test4 
20141028 12:03:42.024551|8 - Message received: test4 

In der obigen Code 'Latenz' ist 40-50μs herum. CountdownEvent Signalisierungsaufruf ist sehr billig (weniger als 50ns), aber T1, T2-Threads sind ausgesetzt und es braucht Zeit, um sie aufzuwecken.

Probe 2

class Program 
{ 
    private static string _msg = string.Empty; 
    private static bool _signal = false; 

    static void Main(string[] args) 
    { 
     while (true) 
     { 
      _signal = false; 
      var t1 = new Thread(Dowork) {Priority = ThreadPriority.Highest}; 
      var t2 = new Thread(Dowork) {Priority = ThreadPriority.Highest}; 
      t1.Start(); 
      t2.Start(); 

      Console.WriteLine("Type message and press [enter] to start"); 
      _msg = Console.ReadLine(); 

      ElapsedLogger.WriteLine("Kick off!"); 
      _signal = true; 

      Thread.Sleep(250); 
      ElapsedLogger.FlushToConsole(); 
     } 
    } 
    private static void Dowork() 
    { 
     string t = Thread.CurrentThread.ManagedThreadId.ToString(); 
     ElapsedLogger.WriteLine("{0} - Waiting...", t); 

     while (!_signal) { Thread.SpinWait(10); } 

     ElapsedLogger.WriteLine("{0} - Message received: {1}", t, _msg); 
    } 
} 

Output:

Type message and press [enter] to start 
testMsg 
20141028 11:56:57.829870|5 - Waiting... 
20141028 11:56:57.830121|6 - Waiting... 
20141028 11:57:05.456075|Kick off! 
20141028 11:57:05.456081|6 - Message received: testMsg 
20141028 11:57:05.456081|5 - Message received: testMsg 

Type message and press [enter] to start 
testMsg2 
20141028 11:57:05.707528|7 - Waiting... 
20141028 11:57:05.707754|8 - Waiting... 
20141028 11:57:57.535549|Kick off! 
20141028 11:57:57.535576|7 - Message received: testMsg2 
20141028 11:57:57.535576|8 - Message received: testMsg2 

Diesmal 'Latenz' ist 6-7μs herum. (aber hohe CPU) Dies ist, weil T1, T2 Threads sind gezwungen, aktiv zu sein (sie tun nichts nur CPU-Zeit zu verbrennen)

In 'echten' Anwendung kann ich CPU so drehen (Ich habe zu viele aktive Threads und es würde es schlimmer machen/langsamer oder sogar den Server töten).

Ist es etwas, was ich stattdessen verwenden kann, um die Latenz auf etwas um 10-15 μs zu reduzieren? Ich denke, mit Producer/Consumer-Muster ist es nicht schneller als mit CountdownEvent. Wait/Pulse ist auch teurer als CountdownEvent.

Was habe ich in Probe 1 die beste, die ich erreichen kann?

Irgendwelche Vorschläge?

Ich werde rohe Sockets auch versuchen, wenn ich eine Zeit habe.

+0

Haben Sie 'ManualResetEventSlim' und' SemaphoreSlim' untersucht? Was ist mit "Monitor.Wait" und "Monitor.Pulse"? –

+0

@JimMischel: Ich habe alle von ihnen versucht und das Ergebnis ist ziemlich das Gleiche. – Novitzky

Antwort

1

Ich stimme zu, dass der SpinWait() - Ansatz für die Produktion nicht realistisch ist. Deine Fäden müssen schlafen gehen und aufgeweckt werden.

Ich sehe, Sie schaut auf warten/Pulse. Haben Sie einen der anderen in .net verfügbaren Primitiven getestet? Joe Albaharis "Threading in C#" hat eine umfassende Übersicht über alle Optionen. http://www.albahari.com/threading/part4.aspx#_Signaling_with_Wait_and_Pulse

Ein Punkt, den ich ansprechen möchte: Wie sicher sind Sie in den Zeitstempeln von ElapsedLogger?

+0

Danke für den Link - ich kenne seine 'Threading in C#' sehr gut. Ich habe versucht, viele andere Dinge zu vergleichen, immer mit den gleichen Einstellungen (Hardware/OS/Optimierung/GC/Jitter/etc). WaitAndPulse Signalisierung gibt mir die gleiche Latenz wie CountdownEvent. Monitor.PulseAll() Aufruf ist teurer als CountdownEvent.Signal() aber beide unter 100-150ns. Bezüglich des ElapsedLoggers liegt die Auflösung weit unter 1us und ich bin mir ziemlich sicher. – Novitzky

1

Es gibt nicht viel zu tun, da der andere Thread vom Betriebssystem geplant werden muss.

Die Erhöhung der Priorität des wartenden Threads ist wahrscheinlich das einzige, was einen großen Unterschied machen kann, und das haben Sie bereits getan. Du könntest noch höher gehen.

Wenn Sie wirklich die geringstmögliche Latenz für die Aktivierung einer anderen Aufgabe benötigen, sollten Sie diese in eine Funktion umwandeln, die direkt vom auslösenden Thread aufgerufen werden kann.

+0

Ben, danke für die Antwort. Zwei Fragen: 1) Wie kann ich mit Thread Priorität noch höher gehen? Der höchste Thread ist vor Threads mit einer anderen Priorität geplant. 2) Wenn ich eine Funktion direkt vom aufrufenden Thread ausführe, wird es überhaupt kein Multithreading geben. Ich werde dann die Aufgabe nacheinander und nicht parallel ausführen. Schlägst du vor, dass diese Funktion stattdessen einen anderen Thread kickt? Ich habe das schon versucht und ich kann keine Verbesserung sehen. – Novitzky

+0

1) Sie haben "Höchste Priorität" verwendet, die nicht so hoch wie THREAD_PRIORITY_TIME_CRITICAL ist. 2) Sie haben gerade drei Aktionen - zwei Threads aufwecken und die aktuelle Funktion fortsetzen. Für welche der drei Aufgaben auch immer diese drei Threads zuständig sind, ist die Latenz, die für den aktuellen Thread am kritischsten ist. –

+0

Danke für THREAD_PRIORITY_TIME_CRITICAL. Ich werde damit spielen. Bezüglich Punkt 2 habe ich in der realen App zwischen 1 und 8 Threads, die latenzkritisch sind (alle müssen von einem anderen Thread ausgelöst werden). – Novitzky

Verwandte Themen