Ist das nicht ein einfacherer und sicherer (und daher besserer) Weg, ein Singleton zu implementieren, anstatt doppeltes Locking von mambo-jambo zu machen? Irgendwelche Nachteile dieses Ansatzes?
%Vor%BEARBEITEN: Der Test auf Thread-Sicherheit ist fehlgeschlagen, kann jemand erklären warum? Wie kommt es, dass Interlocked.CompareExchange nicht wirklich atomar ist?
%Vor%Sie erstellen zwar möglicherweise mehrere Instanzen, aber diese werden mit Garbage Collection gesammelt, da sie nirgends verwendet werden. In keinem Fall ändert die statische _instance-Feldvariable ihren Wert mehr als einmal, der einzige Zeitpunkt, zu dem sie von null auf einen gültigen Wert wechselt. Daher werden Konsumenten dieses Codes immer dieselbe Instanz sehen, obwohl mehrere Instanzen erstellt wurden.
Sperren Sie die freie Programmierung
Joe Duffy , analysiert dies in seinem Buch Concurrent Programming on Windows Sehr Muster, das Sie versuchen, in Kapitel 10, Speichermodelle und Lock Freedom, Seite 526 zu verwenden.
Er bezeichnet dieses Muster als Lazy-Initialisierung einer entspannten Referenz:
%Vor%Wie wir sehen können, sind die obigen Beispielmethoden frei von Sperren zum Preis der Erstellung von wegwerfbaren Objekten. In jedem Fall ändert sich die Value-Eigenschaft für Konsumenten einer solchen API nicht.
Ausgleich von Kompromissen
Lock Freedom hat seinen Preis und muss sorgfältig abgewogen werden. In diesem Fall ist der Preis der Lock-Freiheit, dass Sie Instanzen von Objekten erstellen müssen, die Sie nicht verwenden werden. Dies kann ein akzeptabler Preis sein, den Sie zahlen müssen, da Sie wissen, dass das Risiko von Deadlocks und Threadkonflikten geringer ist.
In dieser bestimmten -Instanz ist die Semantik eines Singletons jedoch im Wesentlichen die Erstellung einer einzelnen Instanz eines Objekts, also würde ich mich lieber für Lazy<T>
, wie @Centro in seiner Antwort zitiert hat.
Dennoch stellt sich immer noch die Frage, wann sollten wir Interlocked.CompareExchange
verwenden? Ich mag dein Beispiel, es ist ziemlich zum Nachdenken anregend und viele Leute sind sehr schnell, es als falsch zu dissen, wenn es nicht schrecklich falsch ist, wie @Blindy zitiert.
Es läuft alles darauf hinaus, ob Sie die Kompromisse berechnet haben und entschieden:
Solange Sie sich der Zielkonflikte bewusst sind und eine bewusste Entscheidung treffen, neue Objekte zu erstellen, damit Sie frei von Sperren sind, könnte Ihr Beispiel auch eine akzeptable Antwort sein.
Wenn Ihr Singleton jemals in Gefahr ist, sich mehrfach zu initialisieren, haben Sie viel schlechtere Probleme. Warum nicht einfach benutzen:
%Vor%Absolut Thread-sicher in Bezug auf die Initialisierung.
Edit: Für den Fall, dass ich nicht klar war, ist dein Code schrecklich falsch . Sowohl der if
check als auch der new
sind nicht threadsicher! Sie müssen eine geeignete Singleton-Klasse verwenden.
Ich bin nicht überzeugt, dass Sie das vollkommen vertrauen können. Ja, Interlocked.CompareExchanger ist atomar, aber New Singleton () wird in keinem nicht-trivialen Fall atomar sein. Da es vor dem Austausch von Werten ausgewertet werden müsste, wäre dies im Allgemeinen keine Thread-sichere Implementierung.
Ihr Singleton-Initialisierer verhält sich genau so, wie er sollte. Siehe Raymond Chens Lock-free-Algorithmen: Der Singleton-Konstruktor :
Dies ist ein Double-Check-Lock, aber ohne die Verriegelung. Anstatt bei der ersten Konstruktion die Sperre zu übernehmen, lassen wir es einfach frei, wer das Objekt erstellen soll. Wenn fünf Threads alle diesen Code gleichzeitig erreichen, sollten wir fünf Objekte erstellen. Nachdem alle erstellt haben, was sie denken, ist das gewinnende Objekt, sie InterlockedCompareExchangePointerRelease aufgerufen, um zu versuchen, den globalen Zeiger zu aktualisieren.
Diese Technik ist geeignet, wenn mehrere Threads versuchen, den Singleton zu erstellen (und alle Verlierer ihre Kopie zerstören). Wenn das Erstellen des Singletons teuer ist oder unerwünschte Nebenwirkungen hat, dann möchten Sie den Free-for-All-Algorithmus nicht verwenden.
Jeder Thread erstellt das Objekt; wie es denkt, dass noch niemand es geschaffen hat. Aber während des InterlockedCompareExchange
ist nur ein -Thread wirklich in der Lage, den globalen Singleton festzulegen .
Dies ist nicht Thread-sicher.
Sie würden eine Sperre benötigen, um if()
und Interlocked.CompareExchange()
zusammen zu halten, und dann brauchen Sie CompareExchange
nicht mehr.
Sie haben immer noch das Problem, dass Sie möglicherweise Instanzen Ihres Singletons erstellen und wegwerfen. Wenn Sie Interlocked.CompareExchange()
ausführen, wird der Konstruktor Singleton
immer ausgeführt, unabhängig davon, ob die Zuweisung erfolgreich ist. Sie sind also nicht besser dran (oder schlechter dran, IMHO), als wenn Sie sagten:
Bessere Leistung bei Threadkonflikten als wenn Sie die Position von lock
und den Test für null vertauscht hätten, aber auf die Gefahr hin, dass eine zusätzliche Instanz erstellt wird.
Die Auto-Eigenschaftsinitialisierung (C # 6.0) scheint nicht die Mehrfachinstanzen von Singleton zu verursachen, die Sie sehen.
%Vor% Ich denke, der einfachste Weg nach .NET 4.0
ist System.Lazy<T>
:
Jon Skeet hat hier einen schönen Artikel hier , der viele Möglichkeiten zur Implementierung von Singleton und den Problemen jedes einzelnen behandelt eins.
Tags und Links .net c# multithreading singleton