Um Ihre Frage zu beantworten, warum es nicht threadsicher ist, instance()
muss der Konstruktor beim ersten Aufruf von nicht aufgerufen werden Singleton 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:
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 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
bevor main()
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.
#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 viel Mist, um "eine bessere Welt zu schaffen".
Die Hauptnachteile dieser Implementierung (wenn ich nicht einige Fehler durchgelassen habe) sind:
- Wenn
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.
- Dies garantiert die Thread-Sicherheit, wenn die Singleton-Instanz nach dem
main()
Aufruf angefordert wird. Wenn Sie sie vorher aufrufen (wie bei der Initialisierung eines statischen Objekts), funktionieren die Dinge möglicherweise nicht, da die CRITICAL_SECTION
möglicherweise nicht initialisiert wird.
- Jedes Mal, wenn eine Instanz angefordert wird, muss eine Sperre vorgenommen 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 Papiere .