Ist die folgende Implementierung des Singleton
(Meyers 'Singleton) -Threads mit verzögerter Initialisierung sicher?
static Singleton& instance()
{
static Singleton s;
return s;
}
Wenn nicht, warum und wie kann der Thread sicher gemacht werden?
Antworten:
In C ++ 11 ist es threadsicher. Gemäß dem Standard ,
§6.7 [stmt.dcl] p4
:Die GCC- und VS-Unterstützung für die Funktion ( Dynamische Initialisierung und Zerstörung mit Parallelität , auch als Magic Statics auf MSDN bezeichnet ) lautet wie folgt:
Vielen Dank an @Mankarse und @olen_gam für ihre Kommentare.
In C ++ 03 war dieser Code nicht threadsicher. Es gibt einen Artikel von Meyers mit dem Titel "C ++ und die Gefahren des Double-Checked Locking", in dem threadsichere Implementierungen des Musters erörtert werden, und die Schlussfolgerung ist mehr oder weniger, dass (in C ++ 03) die Instanziierungsmethode vollständig gesperrt ist Dies ist im Grunde der einfachste Weg, um eine ordnungsgemäße Parallelität auf allen Plattformen sicherzustellen, während die meisten Formen von doppelt überprüften Sperrmustervarianten unter bestimmten Bedingungen unter Rennbedingungen leiden können , es sei denn, Anweisungen sind mit strategisch platzierten Speicherbarrieren verschachtelt.
quelle
Um Ihre Frage zu beantworten, warum es nicht threadsicher ist,
instance()
muss der Konstruktor beim ersten Aufruf von nicht aufgerufen werdenSingleton s
. Um threadsicher zu sein, müsste dies in einem kritischen Abschnitt geschehen, und es gibt im Standard keine Anforderung, dass ein kritischer Abschnitt verwendet wird (der bisherige Standard enthält keine Informationen zu Threads). Compiler implementieren dies häufig durch einfaches Überprüfen und Inkrementieren eines statischen Booleschen Werts - jedoch nicht in einem kritischen Abschnitt. So etwas wie der folgende Pseudocode:Hier ist also ein einfacher thread-sicherer Singleton (für Windows). Es verwendet einen einfachen Klasse - Wrapper für das Windows - CRITICAL_SECTION Objekt , so dass wir den Compiler automatisch initialisiert haben können ,
CRITICAL_SECTION
bevormain()
aufgerufen. Im Idealfall wird eine echte RAII-Klasse für kritische Abschnitte verwendet, die Ausnahmen behandeln kann, die auftreten können, wenn der kritische Abschnitt gehalten wird. Dies würde jedoch den Rahmen dieser Antwort sprengen.Die grundlegende Operation besteht darin, dass, wenn eine Instanz von
Singleton
angefordert wird, eine Sperre aufgehoben wird, der Singleton erstellt wird, wenn dies erforderlich ist, die Sperre aufgehoben und die Singleton-Referenz zurückgegeben wird.Mann - das ist viel Mist, um "eine bessere Welt zu schaffen".
Die Hauptnachteile dieser Implementierung (wenn ich nicht einige Fehler durchgelassen habe) sind:
new Singleton()
geworfen wird, wird das Schloss nicht freigegeben. Dies kann behoben werden, indem ein echtes RAII-Sperrobjekt anstelle des einfachen verwendet wird, das ich hier habe. Dies kann auch dazu beitragen, Dinge portabel zu machen, wenn Sie etwas wie Boost verwenden, um einen plattformunabhängigen Wrapper für das Schloss bereitzustellen.main()
Aufruf angefordert wird. Wenn Sie sie vorher aufrufen (wie bei der Initialisierung eines statischen Objekts), funktionieren die Dinge möglicherweise nicht, da dieCRITICAL_SECTION
möglicherweise nicht initialisiert wird.quelle
new Singleton()
Würfen?new Singleton()
Würfen definitiv ein Problem mit der Sperre. Es sollte eine geeignete RAII-Sperrklasse verwendet werden, etwalock_guard
von Boost. Ich wollte, dass das Beispiel mehr oder weniger in sich geschlossen ist, und es war schon ein bisschen wie ein Monster, also habe ich die Ausnahmesicherheit weggelassen (aber es herausgerufen). Vielleicht sollte ich das beheben, damit dieser Code nicht an einer unangemessenen Stelle ausgeschnitten und eingefügt wird.Mit Blick auf den nächsten Standard (Abschnitt 6.7.4) wird erläutert, wie die statische lokale Initialisierung threadsicher ist. Sobald dieser Standardabschnitt weit verbreitet ist, wird Meyers Singleton die bevorzugte Implementierung sein.
Ich bin mit vielen Antworten bereits nicht einverstanden. Die meisten Compiler implementieren die statische Initialisierung bereits auf diese Weise. Die einzige bemerkenswerte Ausnahme ist Microsoft Visual Studio.
quelle
Die richtige Antwort hängt von Ihrem Compiler ab. Es kann entscheiden , es threadsicher zu machen ; es ist nicht "natürlich" threadsicher.
quelle
Auf den meisten Plattformen ist dies nicht threadsicher. (Fügen Sie den üblichen Haftungsausschluss hinzu, in dem erklärt wird, dass der C ++ - Standard nichts über Threads weiß. Rechtlich sagt er also nicht aus, ob dies der Fall ist oder nicht.)
Der Grund dafür ist nicht, dass nichts mehr als einen Thread daran hindert
s
, den Konstruktor gleichzeitig auszuführen ."C ++ und die Gefahren des Double-Checked Locking" von Scott Meyers und Andrei Alexandrescu ist eine ziemlich gute Abhandlung zum Thema fadensicherer Singletons.
quelle
Wie MSalters sagte: Dies hängt von der von Ihnen verwendeten C ++ - Implementierung ab. Überprüfen Sie die Dokumentation. Was die andere Frage betrifft: "Wenn nicht, warum?" - Der C ++ - Standard erwähnt noch nichts über Threads. Die kommende C ++ - Version kennt jedoch Threads und gibt ausdrücklich an, dass die Initialisierung statischer lokaler Threads threadsicher ist. Wenn zwei Threads eine solche Funktion aufrufen, führt ein Thread eine Initialisierung durch, während der andere blockiert und auf den Abschluss wartet.
quelle