Was sind die Unterschiede zwischen verschiedenen Threading-Synchronisationsoptionen in C #?

164

Kann jemand den Unterschied erklären zwischen:

  • lock (ein Objekt) {}
  • Mutex verwenden
  • Semaphor verwenden
  • Monitor verwenden
  • Verwenden anderer .Net-Synchronisationsklassen

Ich kann es einfach nicht herausfinden. Es scheint mir, dass die ersten beiden gleich sind?

user38834
quelle
Dieser Link hat mir sehr geholfen: albahari.com/threading
Raphael

Antworten:

135

Gute Frage. Ich liege vielleicht falsch. Lassen Sie mich versuchen. Revision Nr. 2 meiner ursprünglichen Antwort. Mit etwas mehr Verständnis. Danke, dass du mich zum Lesen gebracht hast :)

lock (obj)

  • ist ein CLR-Konstrukt für die (objektinterne?) Thread-Synchronisation. Stellt sicher, dass nur ein Thread die Sperre des Objekts übernehmen und den gesperrten Codeblock eingeben kann. Andere Threads müssen warten, bis der aktuelle Eigentümer die Sperre durch Verlassen des Codeblocks aufhebt. Es wird außerdem empfohlen, ein privates Mitgliedsobjekt Ihrer Klasse zu sperren.

Monitore

  • lock (obj) wird intern mit einem Monitor implementiert. Sie sollten die Sperre (obj) bevorzugen, da sie verhindert, dass Sie den Bereinigungsvorgang vergessen. Es ist idiotensicher das Monitor-Konstrukt, wenn Sie so wollen.
    Die Verwendung von Monitor wird im Allgemeinen Mutexen vorgezogen, da Monitore speziell für .NET Framework entwickelt wurden und daher Ressourcen besser nutzen.

Die Verwendung einer Sperre oder eines Monitors ist nützlich, um die gleichzeitige Ausführung von threadsensitiven Codeblöcken zu verhindern. Diese Konstrukte ermöglichen es jedoch nicht, dass ein Thread ein Ereignis an einen anderen kommuniziert. Dies erfordert Synchronisationsereignisse , bei denen es sich um Objekte mit einem von zwei signalisierten und nicht signalisierten Zuständen handelt, mit denen Threads aktiviert und angehalten werden können. Mutex, Semaphore sind Konzepte auf Betriebssystemebene. Beispiel: Mit einem benannten Mutex können Sie mehrere (verwaltete) Exes synchronisieren (um sicherzustellen, dass nur eine Instanz Ihrer Anwendung auf dem Computer ausgeführt wird.)

Mutex:

  • Im Gegensatz zu Monitoren kann ein Mutex jedoch verwendet werden, um Threads prozessübergreifend zu synchronisieren. Bei Verwendung für die Synchronisation zwischen Prozessen wird ein Mutex als benannter Mutex bezeichnet, da er in einer anderen Anwendung verwendet werden soll und daher nicht über eine globale oder statische Variable gemeinsam genutzt werden kann. Es muss ein Name angegeben werden, damit beide Anwendungen auf dasselbe Mutex-Objekt zugreifen können. Im Gegensatz dazu ist die Mutex-Klasse ein Wrapper für ein Win32-Konstrukt. Ein Mutex ist zwar leistungsfähiger als ein Monitor, erfordert jedoch Interop-Übergänge, die rechenintensiver sind als die von der Monitor-Klasse geforderten.

Semaphoren (verletzen mein Gehirn).

  • Verwenden Sie die Semaphore-Klasse, um den Zugriff auf einen Ressourcenpool zu steuern. Threads geben das Semaphor durch Aufrufen der WaitOne-Methode ein, die von der WaitHandle-Klasse geerbt wird, und geben das Semaphor durch Aufrufen der Release-Methode frei. Die Anzahl eines Semaphors wird jedes Mal verringert, wenn ein Thread in das Semaphor eintritt, und erhöht, wenn ein Thread das Semaphor freigibt. Wenn die Anzahl Null ist, werden nachfolgende Anforderungen blockiert, bis andere Threads das Semaphor freigeben. Wenn alle Threads das Semaphor freigegeben haben, liegt die Anzahl auf dem Maximalwert, der beim Erstellen des Semaphors angegeben wurde. Ein Thread kann mehrmals in das Semaphor eintreten. Die Semaphore-Klasse erzwingt keine Thread-Identität bei WaitOne oder Release. Die Programmierer sind dafür verantwortlich, nicht durcheinander zu kommen. Es gibt zwei Arten von Semaphoren: lokale Semaphoren und benannteSystemsemaphoren. Wenn Sie ein Semaphor-Objekt mit einem Konstruktor erstellen, der einen Namen akzeptiert, wird es einem Betriebssystem-Semaphor dieses Namens zugeordnet. Benannte Systemsemaphoren sind im gesamten Betriebssystem sichtbar und können zum Synchronisieren der Aktivitäten von Prozessen verwendet werden. Ein lokales Semaphor existiert nur in Ihrem Prozess. Es kann von jedem Thread in Ihrem Prozess verwendet werden, der auf das lokale Semaphore-Objekt verweist. Jedes Semaphor-Objekt ist ein separates lokales Semaphor.

DIE ZU LESENDE SEITE - Thread-Synchronisation (C #)

Gishu
quelle
18
Sie behaupten, dass Monitordie Kommunikation nicht zulässig ist. Sie können noch Pulseusw. mit einemMonitor
Marc Gravell
3
Schauen Sie sich eine alternative Beschreibung von Semaphoren an - stackoverflow.com/a/40473/968003 . Stellen Sie sich Semaphoren als Türsteher in einem Nachtclub vor. Es gibt eine bestimmte Anzahl von Personen, die gleichzeitig im Club zugelassen sind. Wenn der Club voll ist, darf niemand eintreten, aber sobald eine Person den Club verlässt, kann eine andere Person eintreten.
Alex Klaus
31

Zu "Verwenden anderer .Net-Synchronisationsklassen" - einige der anderen, die Sie kennen sollten:

Es gibt auch mehr (niedrige Overhead-) Sperrkonstrukte in CCR / TPL ( Parallel Extensions CTP) - aber IIRC, diese werden in .NET 4.0 verfügbar gemacht

Marc Gravell
quelle
Wenn ich also eine einfache Signalkommunikation möchte (z. B. Abschluss einer asynchronen Operation), sollte ich Monitor.Pulse? oder SemaphoreSlim oder TaskCompletionSource verwenden?
Vivek
Verwenden Sie TaskCompletionSource für den asynchronen Betrieb. Hören Sie grundsätzlich auf, über Themen nachzudenken, und beginnen Sie, über Aufgaben (Arbeitseinheiten) nachzudenken. Threads sind ein Implementierungsdetail und nicht relevant. Durch die Rückgabe eines TCS können Sie Ergebnisse, Fehler oder die Stornierung zurückgeben und diese können problemlos mit anderen asynchronen Vorgängen (z. B. async await oder ContinueWith) kombiniert werden.
Simon Gillbee
14

Wie in ECMA angegeben und wie Sie anhand der Reflected-Methoden beobachten können, entspricht die lock-Anweisung im Wesentlichen

object obj = x;
System.Threading.Monitor.Enter(obj);
try {
   
}
finally {
   System.Threading.Monitor.Exit(obj);
}

Aus dem oben genannten Beispiel geht hervor, dass Monitore Objekte sperren können.

Mutexe sind nützlich, wenn Sie eine Interprozesssynchronisation benötigen, da sie eine Zeichenfolgenkennung sperren können . Dieselbe Zeichenfolgenkennung kann von verschiedenen Prozessen verwendet werden, um die Sperre zu erhalten.

Semaphoren sind wie Mutexe auf Steroiden. Sie ermöglichen den gleichzeitigen Zugriff, indem sie eine maximale Anzahl gleichzeitiger Zugriffe ermöglichen. Sobald das Limit erreicht ist, blockiert das Semaphor den weiteren Zugriff auf die Ressource, bis einer der Aufrufer das Semaphor freigibt.

arul
quelle
5
Dieser syntaktische Zucker wurde in C # 4 leicht verändert. Check out blogs.msdn.com/ericlippert/archive/2009/03/06/…
Peter Gfader
14

Ich habe die Klassen- und CLR-Unterstützung für das Threading in DotGNU durchgeführt und habe ein paar Gedanken ...

Sofern Sie keine prozessübergreifenden Sperren benötigen, sollten Sie die Verwendung von Mutex & Semaphoren immer vermeiden. Diese Klassen in .NET sind Wrapper für Win32 Mutex und Semaphoren und ziemlich schwer (sie erfordern einen Kontextwechsel in den Kernel, der teuer ist - insbesondere, wenn Ihre Sperre nicht umstritten ist).

Wie bereits erwähnt, ist die C # -Sperranweisung Compiler-Magie für Monitor.Enter und Monitor.Exit (vorhanden innerhalb eines try / finally).

Monitore verfügen über einen einfachen, aber leistungsstarken Signal- / Wartemechanismus, den Mutexe über die Monitor.Pulse / Monitor.Wait-Methoden nicht haben. Das Win32-Äquivalent wären Ereignisobjekte über CreateEvent, die tatsächlich auch in .NET als WaitHandles vorhanden sind. Das Pulse / Wait-Modell ähnelt Unixs pthread_signal und pthread_wait, ist jedoch schneller, da es sich im unbestrittenen Fall vollständig um Operationen im Benutzermodus handeln kann.

Monitor.Pulse / Wait ist einfach zu bedienen. In einem Thread sperren wir ein Objekt, überprüfen ein Flag / einen Status / eine Eigenschaft und rufen Monitor.Wait auf, um die Sperre aufzuheben und zu warten, bis ein Impuls gesendet wird. Wenn die Wartezeit zurückkehrt, kehren wir zurück und überprüfen das Flag / den Status / die Eigenschaft erneut. Im anderen Thread sperren wir das Objekt, wenn wir das Flag / den Status / die Eigenschaft ändern, und rufen dann PulseAll auf, um alle abhörenden Threads zu aktivieren.

Oft möchten wir, dass unsere Klassen threadsicher sind, also setzen wir Sperren in unseren Code. Es ist jedoch häufig der Fall, dass unsere Klasse immer nur von einem Thread verwendet wird. Dies bedeutet, dass die Sperren unseren Code unnötig verlangsamen. Hier können clevere Optimierungen in der CLR dazu beitragen, die Leistung zu verbessern.

Ich bin mir nicht sicher, ob Microsoft Sperren implementiert, aber in DotGNU und Mono wird im Header jedes Objekts ein Sperrstatus-Flag gespeichert. Jedes Objekt in .NET (und Java) kann zu einer Sperre werden, sodass jedes Objekt dies in seinem Header unterstützen muss. In der DotGNU-Implementierung gibt es ein Flag, mit dem Sie eine globale Hashtabelle für jedes Objekt verwenden können, das als Sperre verwendet wird. Dies hat den Vorteil, dass für jedes Objekt ein 4-Byte-Overhead vermieden wird. Dies ist nicht besonders für den Speicher geeignet (insbesondere für eingebettete Systeme, die nicht stark mit Threads versehen sind), beeinträchtigt jedoch die Leistung.

Sowohl Mono als auch DotGNU verwenden effektiv Mutexe, um Sperren / Warten durchzuführen, verwenden jedoch Vergleichs- und Austauschoperationen im Spinlock-Stil , um die Notwendigkeit zu beseitigen, tatsächlich harte Sperren durchzuführen, sofern dies nicht wirklich erforderlich ist:

Hier sehen Sie ein Beispiel, wie Monitore implementiert werden können:

http://cvs.savannah.gnu.org/viewvc/dotgnu-pnet/pnet/engine/lib_monitor.c?revision=1.7&view=markup

tumtumtum
quelle
9

Eine zusätzliche Einschränkung beim Sperren eines gemeinsam genutzten Mutex, den Sie mit einer Zeichenfolgen-ID identifiziert haben, besteht darin, dass standardmäßig ein "lokaler \" Mutex verwendet wird und nicht für Sitzungen in einer Terminalserverumgebung freigegeben wird.

Stellen Sie Ihrer Zeichenfolgenkennung "Global \" voran, um sicherzustellen, dass der Zugriff auf freigegebene Systemressourcen ordnungsgemäß gesteuert wird. Ich hatte gerade eine Menge Probleme beim Synchronisieren der Kommunikation mit einem Dienst, der unter dem SYSTEM-Konto ausgeführt wurde, bevor mir dies klar wurde.

nvuono
quelle
5

Ich würde versuchen, "lock ()", "Mutex" und "Monitor" zu vermeiden, wenn Sie können ...

Schauen Sie sich den neuen Namespace System.Collections.Concurrent in .NET 4 an.
Er enthält einige nette thread-sichere Auflistungsklassen

http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx

ConcurrentDictionary rockt! Keine manuelle Verriegelung mehr für mich!

Peter Gfader
quelle
2
Sperren vermeiden, aber Monitor verwenden? Warum?
Mafu
@mafutrct Weil Sie sich selbst um die Synchronisation kümmern müssen.
Peter Gfader
Oh, jetzt verstehe ich, du wolltest ALLE drei der genannten Ideen vermeiden. Es klang, als würden Sie Monitor verwenden, aber nicht Lock / Mutex.
Mafu
Verwenden Sie niemals System.Collections.Concurrent. Sie sind eine Hauptquelle für Rennbedingungen und blockieren auch den Anrufer-Thread.
Alexander Danilov
-2

In den meisten Fällen sollten Sie keine Sperren (= Monitore) oder Mutexe / Semaphoren verwenden. Sie alle blockieren den aktuellen Thread.

Und du solltest es definitiv nicht benutzen System.Collections.Concurrent Klassen verwenden - sie sind die Hauptursache für Rennbedingungen, da sie keine Transaktionen zwischen mehreren Sammlungen unterstützen und auch den aktuellen Thread blockieren.

Überraschenderweise verfügt .NET nicht über effektive Mechanismen zur Synchronisierung.

Ich habe die serielle Warteschlange von GCD ( Objc/Swiftworld) auf C # implementiert - ein sehr leichtes, nicht blockierendes Synchronisationstool, das den Thread-Pool verwendet, mit Tests.

In den meisten Fällen ist dies der beste Weg, um alles zu synchronisieren - vom Datenbankzugriff (Hallo SQLite) bis zur Geschäftslogik.

Alexander Danilov
quelle