2009-06-07 12 views
12

Was ist es und wie zu verwenden?Gibt es eine Synchronisationsklasse, die die FIFO-Reihenfolge in C# garantiert?

Ich brauche das, da ich einen Timer habe, der jede Sekunde in DB einfügt, und ich habe eine gemeinsame Ressource zwischen Timer-Handler und dem Haupt-Thread. Ich möchte garantieren, dass, wenn der Timer-Handler mehr als eine Sekunde in der Einfügung dauert, die wartenden Threads in der Reihenfolge ausgeführt werden sollten. Dies ist ein Beispielcode für meinen Timer-Handler

private void InsertBasicVaraibles(object param) 
{ 
      try 
      { 
       DataTablesMutex.WaitOne();//mutex for my shared resources 
       //insert into DB 
      } 
      catch (Exception ex) 
      { 
       //Handle 
      } 
      finally 
      { 
       DataTablesMutex.ReleaseMutex(); 
      } 
} 

Aber zur Zeit der Mutex bietet keine Garantie für jede Bestellung. Es gibt keine Antwort, nachdem ich die detaillierte Frage gestellt habe !!!

+0

per Definition ein Sperr-FIFO ist. Ein Thread geht rein und niemand kommt rein bis es rauskommt! –

+10

@Mitch: Aber es gibt keine Garantie, dass der erste Thread, der warten musste, derjenige ist, der unmittelbar danach entsperrt wird. Also nein, Schlösser sind keine FIFOs. –

+2

(d. H. Wenn T1 derzeit die Sperre hält, dann versucht T2 sie zu erfassen, dann versucht T3, sie zu erhalten, es gibt keine Garantie, dass T3 sie vor T2 erhält.) –

Antwort

26

Sie müssen Ihre eigene Klasse schreiben, dies zu tun, fand ich dieses Beispiel aus (geklebt, weil es so aussieht, als wenn die Domain der Website abgelaufen ist):

using System.Threading; 

public sealed class QueuedLock 
{ 
    private object innerLock; 
    private volatile int ticketsCount = 0; 
    private volatile int ticketToRide = 1; 

    public QueuedLock() 
    { 
     innerLock = new Object(); 
    } 

    public void Enter() 
    { 
     int myTicket = Interlocked.Increment(ref ticketsCount); 
     Monitor.Enter(innerLock); 
     while (true) 
     { 

      if (myTicket == ticketToRide) 
      { 
       return; 
      } 
      else 
      { 
       Monitor.Wait(innerLock); 
      } 
     } 
    } 

    public void Exit() 
    { 
     Interlocked.Increment(ref ticketToRide); 
     Monitor.PulseAll(innerLock); 
     Monitor.Exit(innerLock); 
    } 
} 

Anwendungsbeispiel:

QueuedLock queuedLock = new QueuedLock(); 

try 
{ 
    queuedLock.Enter(); 
    // here code which needs to be synchronized 
    // in correct order 
} 
finally 
{ 
    queuedLock.Exit(); 
} 

Source via Google cache

+4

Dies wäre schöner, wenn QueuedLock IDisposable –

+0

Nowdays Weitergabe von flüchtigen durch ref nicht von C# -Compiler ermutigt wird, gibt es die Warnung, dass es nicht sein wird als flüchtig behandelt. Ansonsten eine perfekte Lösung für mein heutiges Problem! – AlexanderVX

+0

Arbeitete perfekt! – merger

1

Es gibt keine garantierte Bestellung auf jedem integrierten Synchronisationsobjekte: http://msdn.microsoft.com/en-us/library/ms684266(VS.85).aspx

Wenn Sie eine garantierte Bestellung mögen Sie versuchen müssen werden und selbst etwas zu bauen, beachten Sie aber, dass es nicht so einfach, wie es klingen mag , insbesondere wenn mehrere Threads gleichzeitig (fast) den Synchronisationspunkt erreichen. Bis zu einem gewissen Grad wird die Reihenfolge, in der sie veröffentlicht werden, immer "zufällig" sein, da Sie nicht vorhersagen können, in welcher Reihenfolge der Punkt erreicht wird, also ist es wirklich wichtig?

+0

Der Link bezieht sich natürlich auf die OS-Primitive ... irgendwann werden die .NET-Primitive darauf gebaut, also denke ich, dass FIFO nicht garantiert werden kann ... siehe auch Jon's Antwort. – jerryjvl

+1

Das Problem besteht nicht generell darin, dass Threads in zufälliger Reihenfolge in das Schloss gelangen, wenn alle gleichzeitig versuchen, das Schloss zu greifen. Das Problem tritt auf, wenn Threads, die lange darauf gewartet haben, in die Sperre zu gelangen, von Threads in die Warteschlange geschnitten werden, die viel später in die Sperre eindringen wollten. Stellen Sie sich einen ausgelasteten Webserver vor, auf dem 1000 Anfragen ständig auf eine freigegebene Ressource zugreifen und der erste Thread, der auf eine Sperre wartet, wartet, weil spätere Anfragen die Sperre weiter stehlen, bevor sie dran sind. –

10

Gerade lesen Joe Duffy "Concurrent-Programmierung unter Windows" es klingt wie Sie in der Regel erhalten FIFO-Verhalten von .NET-Monitoren, aber es gibt einige Situationen, in denen das nicht auftreten.

Seite 273 des Buches sagt: "Da Monitore Kernel-Objekte intern verwenden, zeigen sie das gleiche Grob-FIFO-Verhalten, das auch die OS-Synchronisationsmechanismen aufweisen (im vorherigen Kapitel beschrieben). Monitore sind ungerecht, also wenn ein anderer Thread schleicht sich ein und erwirbt das Schloss, bevor ein erwachter wartender Faden versucht, das Schloss zu bekommen, der hinterhältige Thread darf das Schloss erwerben. "

Ich kann nicht sofort den „im vorigen Kapitel“ verwies Abschnitt finden, aber es tut Note, die Schlösser in der letzten Versionen von Windows bewusst unfairen gemacht worden Skalierbarkeit zu verbessern und Lock Konvois zu reduzieren.

Müssen Sie Ihr Schloss definitiv FIFO? Vielleicht gibt es eine andere Art, das Problem anzugehen. Ich kenne keine Locks in .NET, die garantiert FIFO sind.

+0

Jon Skeet ist wie üblich, korrekt - Win2k3 SP2 (ish?) Und oben haben keine 100% fair locking, und das ist Absicht. –

+4

-1: keine echte Lösung zur Verfügung gestellt – Svisstack

+0

Was wirklich Spaß ist, wenn der "hinterhältige Thread" auch derjenige ist, der gerade den Monitor freigegeben hat. Und so bleibt es für immer in einer Schleife und verhungert den anderen Thread. Gerade lief dieses Verhalten mit einigen alten Code in .NET Compact Framework auf CE 5. – reirab

7

Sie sollten Ihr System neu entwerfen, um nicht auf die Ausführungsreihenfolge der Fäden zu verlassen. Anstatt beispielsweise Ihre Threads einen DB-Aufruf erstellen zu lassen, der mehr als eine Sekunde dauern kann, platzieren Sie Ihre Threads den Befehl, den sie ausführen würden, in eine Datenstruktur wie eine Warteschlange (oder einen Heap, wenn etwas besagt, dass dies der Fall sein sollte vor einem anderen sein "). In der Freizeit entleeren Sie die Warteschlange und fügen Sie Ihre db nacheinander in der richtigen Reihenfolge ein.Follow-up auf Matthew Brindley Antwort

1

Eigentlich sind die Antworten gut, aber ich löste das Problem, indem Sie den Timer zu entfernen und die Methode (Timer-Handler zuvor) in Hintergrund-Thread laufen als

private void InsertBasicVaraibles() 
    { 
     int functionStopwatch = 0; 
     while(true) 
     { 

      try 
      { 
      functionStopwatch = Environment.TickCount; 
      DataTablesMutex.WaitOne();//mutex for my shared resources 
      //insert into DB 
      } 
      catch (Exception ex) 
      { 
      //Handle    
      } 
      finally    
      {     
       DataTablesMutex.ReleaseMutex(); 
      } 

      //simulate the timer tick value 
      functionStopwatch = Environment.TickCount - functionStopwatch; 
      int diff = INSERTION_PERIOD - functionStopwatch; 
      int sleep = diff >= 0 ? diff:0; 
      Thread.Sleep(sleep); 
     } 
    } 
+0

schöne Schlaflogik! jetzt um meinen ganzen Code neu zu schreiben :) – divinci

0

folgt.

Wenn Code Umwandlung von

lock (LocalConnection.locker) {...} 

dann könnte man entweder eine IDisposable tun oder tun, was ich tat:

public static void Locking(Action action) { 
    Lock(); 
    try { 
    action(); 
    } finally { 
    Unlock(); 
    } 
} 

LocalConnection.Locking(() => {...}); 

ich gegen IDisposable entschieden, weil es ein neues unsichtbares Objekt bei jedem Aufruf erzeugt würde .

Was reentrancy Ausgabe geändert ich den Code dazu:

public sealed class QueuedLock { 
    private object innerLock = new object(); 
    private volatile int ticketsCount = 0; 
    private volatile int ticketToRide = 1; 
    ThreadLocal<int> reenter = new ThreadLocal<int>(); 

    public void Enter() { 
     reenter.Value++; 
     if (reenter.Value > 1) 
      return; 
     int myTicket = Interlocked.Increment(ref ticketsCount); 
     Monitor.Enter(innerLock); 
     while (true) { 
      if (myTicket == ticketToRide) { 
       return; 
      } else { 
       Monitor.Wait(innerLock); 
      } 
     } 
    } 

    public void Exit() { 
     if (reenter.Value > 0) 
      reenter.Value--; 
     if (reenter.Value > 0) 
      return; 
     Interlocked.Increment(ref ticketToRide); 
     Monitor.PulseAll(innerLock); 
     Monitor.Exit(innerLock); 
    } 
} 
Verwandte Themen