Das übliche Muster für eine Singleton-Klasse ist so etwas wie
static Foo &getInst()
{
static Foo *inst = NULL;
if(inst == NULL)
inst = new Foo(...);
return *inst;
}
Nach meinem Verständnis ist diese Lösung jedoch nicht threadsicher, da 1) der Konstruktor von Foo möglicherweise mehrmals aufgerufen wird (was möglicherweise von Bedeutung ist oder nicht) und 2) inst möglicherweise nicht vollständig erstellt wird, bevor er an einen anderen Thread zurückgegeben wird .
Eine Lösung besteht darin, einen Mutex um die gesamte Methode zu wickeln, aber dann zahle ich für den Synchronisationsaufwand, lange nachdem ich ihn tatsächlich benötige. Eine Alternative ist so etwas wie
static Foo &getInst()
{
static Foo *inst = NULL;
if(inst == NULL)
{
pthread_mutex_lock(&mutex);
if(inst == NULL)
inst = new Foo(...);
pthread_mutex_unlock(&mutex);
}
return *inst;
}
Ist dies der richtige Weg, oder gibt es Fallstricke, die ich beachten sollte? Gibt es beispielsweise Probleme mit der statischen Initialisierungsreihenfolge, die auftreten können, dh ist inst immer garantiert NULL, wenn getInst zum ersten Mal aufgerufen wird?
quelle
Antworten:
Ihre Lösung heißt "Double Checked Locking" und die Art und Weise, wie Sie sie geschrieben haben, ist nicht threadsicher.
Dieses Papier von Meyers / Alexandrescu erklärt, warum - aber dieses Papier wird auch weitgehend missverstanden. Es wurde das Mem "Double Checked Locking ist in C ++ unsicher" gestartet. Die tatsächliche Schlussfolgerung lautet jedoch, dass Double Checked Locking in C ++ sicher implementiert werden kann. Es erfordert lediglich die Verwendung von Speicherbarrieren an einer nicht offensichtlichen Stelle.
Das Dokument enthält einen Pseudocode, der zeigt, wie Speicherbarrieren zur sicheren Implementierung des DLCP verwendet werden. Daher sollte es für Sie nicht schwierig sein, Ihre Implementierung zu korrigieren.
quelle
Wenn Sie C ++ 11 verwenden, ist dies der richtige Weg:
Foo& getInst() { static Foo inst(...); return inst; }
Nach dem neuen Standard besteht keine Notwendigkeit mehr, sich um dieses Problem zu kümmern. Die Objektinitialisierung wird nur von einem Thread durchgeführt, andere Threads warten, bis sie abgeschlossen ist. Oder Sie können std :: call_once verwenden. (mehr Infos hier )
quelle
Herb Sutter spricht über die doppelt überprüfte Verriegelung in der CppCon 2014.
Unten ist der Code, den ich basierend darauf in C ++ 11 implementiert habe:
class Foo { public: static Foo* Instance(); private: Foo() {} static atomic<Foo*> pinstance; static mutex m_; }; atomic<Foo*> Foo::pinstance { nullptr }; std::mutex Foo::m_; Foo* Foo::Instance() { if(pinstance == nullptr) { lock_guard<mutex> lock(m_); if(pinstance == nullptr) { pinstance = new Foo(); } } return pinstance; }
Sie können das vollständige Programm auch hier überprüfen: http://ideone.com/olvK13
quelle
static Foo foo;
undreturn &foo;
in der Instanz - Funktion wäre genug;static
Die Initialisierung ist in C ++ 11 threadsicher. Verweisen Sie jedoch lieber auf Zeiger.Verwenden Sie
pthread_once
diese Option , um sicherzustellen, dass die Initialisierungsfunktion einmal atomar ausgeführt wird.(Unter Mac OS X wird eine Drehsperre verwendet. Sie kennen die Implementierung anderer Plattformen nicht.)
quelle
TTBOMK, die einzige garantierte thread-sichere Möglichkeit, dies ohne Sperren zu tun, besteht darin, alle Ihre Singletons zu initialisieren, bevor Sie jemals einen Thread starten.
quelle
Ihre Alternative heißt "doppelt geprüftes Sperren" .
Es könnte Multithread-Speichermodelle geben, in denen es funktioniert, aber POSIX garantiert keines
quelle
Die ACE-Singleton-Implementierung verwendet ein doppelt geprüftes Sperrmuster für die Thread-Sicherheit. Sie können darauf verweisen, wenn Sie möchten.
Den Quellcode finden Sie hier .
quelle
Funktioniert TLS hier? https://en.wikipedia.org/wiki/Thread-local_storage#C_and_C++
Zum Beispiel,
static _thread Foo *inst = NULL; static Foo &getInst() { if(inst == NULL) inst = new Foo(...); return *inst; }
Wir brauchen aber auch eine Möglichkeit, es explizit zu löschen, wie
static void deleteInst() { if (!inst) { return; } delete inst; inst = NULL; }
quelle
Die Lösung ist wegen der Anweisung nicht threadsicher
inst = new Foo();
kann vom Compiler in zwei Anweisungen unterteilt werden:
Anweisung1: inst = malloc (sizeof (Foo));
Anweisung2: inst-> Foo ();
Angenommen, nach Ausführung von Anweisung 1 durch einen Thread erfolgt ein Kontextwechsel. Und der 2. Thread führt auch die
getInstance()
Methode aus. Dann wird der 2. Thread feststellen, dass der 'inst'-Zeiger nicht null ist. Der zweite Thread gibt also einen Zeiger auf ein nicht initialisiertes Objekt zurück, da der Konstruktor vom ersten Thread noch nicht aufgerufen wurde.quelle