Ist die Meyers-Implementierung des Singleton-Musterthreads sicher?

Lesezeit: 7 Minuten

Ist die Meyers Implementierung des Singleton Musterthreads sicher
TL36

Ist die folgende Implementierung mit verzögerter Initialisierung von Singleton (Meyers’ Singleton) Thread sicher?

static Singleton& instance()
{
     static Singleton s;
     return s;
}

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).

    – 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

1647140425 351 Ist die Meyers Implementierung des Singleton Musterthreads sicher
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.

GCC- und VS-Unterstützung für das Feature (Dynamische Initialisierung und Zerstörung mit Parallelitätauch bekannt als Magic Statics auf MSDN) ist wie folgt:

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.

  • Es gibt auch eine ausführliche Diskussion über das Singleton-Muster (Lebensdauer und Threadsicherheit) von Alexandrescu in Modern C++ Design. Siehe Lokis Seite: loki-lib.sourceforge.net/index.php?n=Pattern.Singleton

    – Matthias M.

    2. November 2009 um 14:57 Uhr

  • 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

Ist die Meyers Implementierung des Singleton Musterthreads sicher
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 macht man es threadsicher?

“C++ und die Gefahren des doppelt geprüften Sperrens” von Scott Meyers und Andrei Alexandrescu ist eine ziemlich gute Abhandlung zum Thema Thread-sichere Singletons.

1647140428 561 Ist die Meyers Implementierung des Singleton Musterthreads sicher
Sellibitze

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.

995780cookie-checkIst die Meyers-Implementierung des Singleton-Musterthreads sicher?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy