Wenn nicht, warum und wie kann man es threadsicher machen?
Kann mir bitte jemand erklären, warum dies nicht Thread-sicher ist. Die Artikel, die in den Links erwähnt werden, diskutieren die Thread-Sicherheit mit einer alternativen Implementierung (mit einer Zeigervariablen, dh einem statischen Singleton *pInstance).
Mögliches Duplikat der Singleton-Instanz, die als statische Variable der GetInstance-Methode deklariert ist
– Trevor Boyd Smith
4. September 2018 um 11:45 Uhr
Groo
Im C++11, es ist threadsicher. Entsprechend der Standard, §6.7 [stmt.dcl] p4:
Wenn die Steuerung gleichzeitig in die Deklaration eintritt, während die Variable initialisiert wird, wird die gleichzeitige Ausführung wartet zum Abschluss der Initialisierung.
Danke an @Mankarse und @olen_gam für ihre Kommentare.
Im C++03, war dieser Code nicht threadsicher. Es gibt einen Artikel von Meyers namens “C++ und die Gefahren des doppelt geprüften Sperrens” die Thread-sichere Implementierungen des Musters diskutiert, und die Schlussfolgerung ist mehr oder weniger, dass (in C++03) das vollständige Sperren um die Instanziierungsmethode im Grunde der einfachste Weg ist, um eine ordnungsgemäße Parallelität auf allen Plattformen sicherzustellen, während die meisten Formen doppelt sind -geprüfte Sperrmustervarianten können auf bestimmten Architekturen unter Racebedingungen leiden, es sei denn, Anweisungen sind mit strategisch platzierten Speicherbarrieren verschachtelt.
Mit boost::call_once können Sie einen Thread-sicheren Singleton erstellen.
– Goldesel
25. Januar 2012 um 15:03 Uhr
Leider ist dieser Teil des Standards nicht im Visual Studio 2012 C++ Compiler implementiert. Wird hier in der Tabelle „C++11 Core Language Features: Concurrency“ als „Magic Statics“ bezeichnet: msdn.microsoft.com/en-us/library/vstudio/hh567368.aspx
– olen_garn
27. März 2013 um 16:41 Uhr
Der Ausschnitt aus dem Standard befasst sich mit der Konstruktion, aber nicht mit der Zerstörung. Verhindert der Standard, dass das Objekt in einem Thread zerstört wird, während (oder bevor) ein anderer Thread versucht, bei der Programmbeendigung darauf zuzugreifen?
– Eintopfbasic
10. Juli 2017 um 0:50 Uhr
IANA(C++-Sprache)L aber Abschnitt 3.6.3 [basic.start.term] p2 schlägt vor, dass es möglich ist, undefiniertes Verhalten zu erreichen, indem versucht wird, auf das Objekt zuzugreifen, nachdem es zerstört wurde?
– Eintopfbasic
10. Juli 2017 um 0:58 Uhr
Michael Burr
Um Ihre Frage zu beantworten, warum es nicht threadsicher ist, liegt es nicht daran, dass der erste Aufruf erfolgt instance() muss den Konstruktor für aufrufen Singleton s. Um threadsicher zu sein, müsste dies in einem kritischen Abschnitt erfolgen, und im Standard ist es jedoch nicht erforderlich, dass ein kritischer Abschnitt verwendet wird (der Standard ist bis heute völlig still zu Threads). Compiler implementieren dies oft durch eine einfache Überprüfung und Inkrementierung eines statischen booleschen Werts – aber nicht in einem kritischen Abschnitt. Etwas wie der folgende Pseudocode:
static Singleton& instance()
{
static bool initialized = false;
static char s[sizeof( Singleton)];
if (!initialized) {
initialized = true;
new( &s) Singleton(); // call placement new on s to construct it
}
return (*(reinterpret_cast<Singleton*>( &s)));
}
Hier ist also ein einfaches Thread-sicheres Singleton (für Windows). Es verwendet einen einfachen Klassen-Wrapper für das Windows-Objekt CRITICAL_SECTION, sodass wir den Compiler automatisch initialisieren können CRITICAL_SECTION Vor main() wird genannt. Idealerweise würde eine echte RAII-Klasse für kritische Abschnitte verwendet, die Ausnahmen behandeln kann, die auftreten können, wenn der kritische Abschnitt gehalten wird, aber das würde den Rahmen dieser Antwort sprengen.
Die grundlegende Operation ist die, wenn eine Instanz von Singleton angefordert, eine Sperre genommen, das Singleton erstellt, falls erforderlich, dann die Sperre freigegeben und die Singleton-Referenz zurückgegeben.
#include <windows.h>
class CritSection : public CRITICAL_SECTION
{
public:
CritSection() {
InitializeCriticalSection( this);
}
~CritSection() {
DeleteCriticalSection( this);
}
private:
// disable copy and assignment of CritSection
CritSection( CritSection const&);
CritSection& operator=( CritSection const&);
};
class Singleton
{
public:
static Singleton& instance();
private:
// don't allow public construct/destruct
Singleton();
~Singleton();
// disable copy & assignment
Singleton( Singleton const&);
Singleton& operator=( Singleton const&);
static CritSection instance_lock;
};
CritSection Singleton::instance_lock; // definition for Singleton's lock
// it's initialized before main() is called
Singleton::Singleton()
{
}
Singleton& Singleton::instance()
{
// check to see if we need to create the Singleton
EnterCriticalSection( &instance_lock);
static Singleton s;
LeaveCriticalSection( &instance_lock);
return s;
}
Mann – das ist eine Menge Mist, um “global besser zu machen”.
Die Hauptnachteile dieser Implementierung (wenn ich nicht einige Fehler durchschlüpfen ließ) sind:
wenn new Singleton() wirft, wird die Sperre nicht freigegeben. Dies kann behoben werden, indem anstelle des einfachen Objekts, das ich hier habe, ein echtes RAII-Sperrobjekt verwendet wird. Dies kann auch dazu beitragen, Dinge portabel zu machen, wenn Sie etwas wie Boost verwenden, um einen plattformunabhängigen Wrapper für die Sperre bereitzustellen.
dies garantiert Thread-Sicherheit, wenn die Singleton-Instanz danach angefordert wird main() aufgerufen wird – wenn Sie es vorher aufrufen (wie bei der Initialisierung eines statischen Objekts), funktionieren die Dinge möglicherweise nicht, weil die CRITICAL_SECTION möglicherweise nicht initialisiert.
Jedes Mal, wenn eine Instanz angefordert wird, muss eine Sperre ausgeführt werden. Wie gesagt, dies ist eine einfache Thread-sichere Implementierung. Wenn Sie eine bessere benötigen (oder wissen möchten, warum Dinge wie die Double-Check-Lock-Technik fehlerhaft sind), lesen Sie die in Groos Antwort verlinkten Dokumente.
Äh oh. Was passiert wenn new Singleton() wirft?
– sbi
2. November 2009 um 17:51 Uhr
@Bob – um fair zu sein, mit einem richtigen Satz von Bibliotheken würde der ganze Cruft, der mit Nichtkopierbarkeit und einer richtigen RAII-Sperre zu tun hat, verschwinden oder minimal sein. Aber ich wollte, dass das Beispiel einigermaßen in sich geschlossen ist. Obwohl Singletons eine Menge Arbeit für vielleicht minimalen Gewinn bedeuten, fand ich sie nützlich, um die Verwendung von Globals zu verwalten. Sie neigen dazu, leichter herauszufinden, wo und wann sie verwendet werden, ein wenig besser als nur eine Namenskonvention.
– Michael Burr
2. November 2009 um 18:01 Uhr
@sbi: In diesem Beispiel, wenn new Singleton() wirft gibt es definitiv ein Problem mit dem Schloss. Es sollte eine geeignete RAII-Sperrklasse verwendet werden, so etwas wie lock_guard von Boost. Ich wollte, dass das Beispiel mehr oder weniger in sich geschlossen ist, und es war schon ein bisschen ein Monster, also habe ich die Ausnahmesicherheit weggelassen (aber es ausgerufen). Vielleicht sollte ich das beheben, damit dieser Code nicht irgendwo unangemessen ausgeschnitten und eingefügt wird.
– Michael Burr
2. November 2009 um 18:17 Uhr
Warum den Singleton dynamisch zuweisen? Warum nicht einfach ‘pInstance’ zu einem statischen Mitglied von ‘Singleton::instance()’ machen?
– Martin York
2. November 2009 um 19:41 Uhr
@ Martin – fertig. Sie haben Recht, das macht es ein bisschen einfacher – es wäre noch besser, wenn ich eine RAII-Lock-Klasse verwenden würde.
– Michael Burr
2. November 2009 um 22:45 Uhr
Ein Blick auf den nächsten Standard (Abschnitt 6.7.4) erklärt, wie die statische lokale Initialisierung Thread-sicher ist. Sobald dieser Abschnitt des Standards weit verbreitet ist, wird Meyers Singleton die bevorzugte Implementierung sein.
Ich bin schon mit vielen Antworten nicht einverstanden. Die meisten Compiler implementieren die statische Initialisierung bereits auf diese Weise. Die einzige bemerkenswerte Ausnahme ist Microsoft Visual Studio.
Die richtige Antwort hängt von Ihrem Compiler ab. Es kann sich dafür entscheiden machen es threadsicher; es ist nicht “natürlich” threadsicher.
Ist die folgende Implementierung […] Thread sicher?
Auf den meisten Plattformen ist dies nicht Thread-sicher. (Fügen Sie den üblichen Haftungsausschluss hinzu, der erklärt, dass der C++-Standard nichts über Threads weiß, also sagt er rechtlich nicht, ob er es ist oder nicht.)
Wenn nicht, warum […]?
Der Grund dafür ist, dass nichts die gleichzeitige Ausführung von mehr als einem Thread verhindert s‘ Konstrukteur.
Wie MSalters sagte: Es hängt von der C++-Implementierung ab, die Sie verwenden. Überprüfen Sie die Dokumentation. Zur anderen Frage: “Wenn nein, warum?” — Der C++-Standard erwähnt noch nichts über Threads. Aber die kommende C++-Version kennt Threads und gibt explizit an, dass die Initialisierung von statischen Locals Thread-sicher ist. Wenn zwei Threads eine solche Funktion aufrufen, führt ein Thread eine Initialisierung durch, während der andere blockiert und auf deren Beendigung wartet.
9957800cookie-checkIst die Meyers-Implementierung des Singleton-Musterthreads sicher?yes
Kann mir bitte jemand erklären, warum dies nicht Thread-sicher ist. Die Artikel, die in den Links erwähnt werden, diskutieren die Thread-Sicherheit mit einer alternativen Implementierung (mit einer Zeigervariablen, dh einem statischen Singleton *pInstance).
– TL36
2. November 2009 um 14:50 Uhr
Siehe: stackoverflow.com/questions/449436/…
– Martin York
2. November 2009 um 19:37 Uhr
Siehe: stackoverflow.com/questions/1008019/c-singleton-design-pattern/…
– Martin York
2. November 2009 um 19:43 Uhr
Mögliches Duplikat der Singleton-Instanz, die als statische Variable der GetInstance-Methode deklariert ist
– Trevor Boyd Smith
4. September 2018 um 11:45 Uhr