Frage

Gibt es eine Möglichkeit zu implementieren, die ein singleton-Objekt in C++:

  1. Träge konstruiert, in einer thread-sicheren Weise (zwei threads können gleichzeitig der erste Benutzer, der die singleton - es sollen nur noch gebaut werden, einmal).
  2. Nicht verlassen sich auf statische Variablen konstruiert zu haben (so das singleton-Objekt selbst ist sicher zu verwenden, während der Bau von statischen Variablen).

(Ich weiß nicht, meine C++ gut genug, aber ist es der Fall, dass die Integrale und Konstanten statischen Variablen initialisiert werden, bevor code ausgeführt wird (dh, auch vor statischen Konstruktoren ausgeführt werden - Ihre Werte kann schon sein "initialisiert" in das Programm-image)?Wenn dem so ist - vielleicht kann dies ausgenutzt werden, zur Implementierung eines singleton-mutex - die können wiederum verwendet werden, um schützen die Schöpfung des wirklichen singleton -..)


Hervorragend, es scheint, dass ich ein paar gute Antworten (eine Schande, ich kann nicht markieren, 2 oder 3 als die Antwort).Es scheint zwei große Lösungen:

  1. Verwenden Sie statische Initialisierung (als Gegensatz zu dynamischen Initialisierung) einer POD statische variable, und die Umsetzung meiner eigenen mutex mit, dass mit dem builtin atomaren Anweisungen.Dies war die Art von Lösung, die ich war, deutete an, in meiner Frage, und ich glaube, das wusste ich schon.
  2. Einige andere Bibliothek-Funktion wie pthread_once oder boost::call_once.Diese habe ich sicherlich noch nicht kennen - und bin sehr dankbar für die Antworten.
War es hilfreich?

Lösung

Im Grunde hast du Fragen synchronisierte Erstellung eines singleton, ohne Synchronisation (die zuvor konstruierten, Variablen).Im Allgemeinen, Nein, das ist nicht möglich.Sie brauchen etwas für die Synchronisation verfügbar.

Für Ihre andere Frage, ja, statische Variablen werden kann statisch initialisiert (d.h.keine runtime-code erforderlich) garantiert initialisiert werden, bevor andere code ausgeführt wird.Dies macht es möglich, einen statisch initialisierten mutex zu synchronisieren Erstellung des singleton.

Aus der 2003 revision des C++ - standard:

Objekte mit statischer Speicherdauer (3.7.1) werden auf null initialisiert (8.5), bevor andere Initialisierung stattfindet.Null-Initialisierung und Initialisierung mit ein konstanter Ausdruck sein, werden zusammenfassend als statische Initialisierung;alle anderen Initialisierung dynamische Initialisierung.Objekte von POD-Typen (3.9) mit statischer Speicherdauer initialisiert mit konstanter Ausdruck (5.19) muss initialisiert werden, bevor alle dynamischen Initialisierung stattfindet.Objekte mit statischer Speicherdauer im namespace-Bereich in der gleichen übersetzungseinheit und dynamisch initialisiert werden initialisiert, in der Reihenfolge, in der Ihre definition wird in der übersetzung Einheit.

Wenn Sie weiß dass Sie sich mit dieser singleton während der Initialisierung der andere statische Objekte, ich denke, Sie werden feststellen, dass die Synchronisierung ist ein nicht-Thema.Zu den besten meines Wissens, alle gängigen Compiler initialisieren von statischen Objekten in einem einzigen thread, thread-Sicherheit während der statischen Initialisierung.Können Sie erklären, Ihre singleton-Zeiger auf NULL, und dann überprüfen, um zu sehen, ob es initialisiert wurde, bevor Sie es verwenden.

Allerdings setzt dies Voraus, dass Sie weiß Sie verwenden dieses singleton während der statischen Initialisierung.Dies ist auch nicht garantiert, durch standard, so wenn Sie wollen werden vollständig sicher ist, verwenden Sie einen statisch initialisierten mutex.

Edit:Chris ' Vorschlag, eine Atomare compare-and-swap würde sicherlich funktionieren.Wenn Portabilität ist nicht ein Problem (und eine zusätzliche temporäre singletons ist kein problem), dann ist es eine etwas geringere overhead-Lösung.

Andere Tipps

Leider, Matt-Antwort-Funktionen, was heißt double-checked locking die ist nicht unterstützt durch die C/C++ - Speichermodell.(Es wird unterstützt von der Java 1.5 und höher — und ich denke .NET — Speicher-Modell.) Dies bedeutet, dass zwischen der Zeit, als die pObj == NULL überprüfen Sie stattfindet, und, wenn die lock (mutex) erworben, pObj möglicherweise bereits zugewiesen wurde, auf einem anderen thread.Thread-Umschaltung geschieht, wenn die OS will es, nicht zwischen den "Zeilen" eines Programms (die keine Bedeutung haben nach der Kompilierung in den meisten Sprachen).

Außerdem, wie Matt erkennt, verwendet er einen int wie ein Schloss eher als ein OS primitiv.Tun Sie das nicht.Richtige Schlösser erfordern den Einsatz des memory-Barriere Anweisungen, potenziell cache-line Flush und so weiter;verwenden Sie Ihre Betriebssystem-primitive für die Verriegelung.Dies ist besonders wichtig, da die primitiven verwendet, können Wechsel zwischen den einzelnen CPU-Linien, die von Ihrem Betriebssystem läuft auf;was funktioniert auf einer CPU Foo funktioniert möglicherweise nicht auf die CPU-Foo2.Die meisten Betriebssysteme entweder nativ Unterstützung für POSIX-threads (pthreads) oder bieten Sie als wrapper für die OS-threading-Paket, so ist es oft am besten, zu illustrieren Beispiele für die Verwendung von Ihnen.

Wenn Ihr Betriebssystem bietet entsprechende primitive, und wenn Sie absolut es brauchen für Leistung, anstatt das zu tun diese Art der Verriegelung/Initialisierung können Sie mit einer atomic compare und swap Betrieb zu initialisieren einer gemeinsamen globalen Variablen.Im wesentlichen, was Sie schreiben, wird so Aussehen:

MySingleton *MySingleton::GetSingleton() {
    if (pObj == NULL) {
        // create a temporary instance of the singleton
        MySingleton *temp = new MySingleton();
        if (OSAtomicCompareAndSwapPtrBarrier(NULL, temp, &pObj) == false) {
            // if the swap didn't take place, delete the temporary instance
            delete temp;
        }
    }

    return pObj;
}

Dies funktioniert nur, wenn es sicher ist, erstellen Sie mehrere Instanzen des singleton (eine pro thread, passiert aufrufen GetSingleton() gleichzeitig), und werfen Sie dann extras entfernt.Die OSAtomicCompareAndSwapPtrBarrier Funktion auf Mac OS X — die meisten Betriebssysteme bieten eine ähnlich primitive — prüft, ob pObj ist NULL und nur legt es auf temp zu es wenn es ist.Dies nutzt die hardware-Unterstützung wirklich, buchstäblich nur die swap einmal und sagen, ob es passiert ist.

Eine Anlage zu nutzen, wenn Ihr Betriebssystem bietet es in-zwischen diesen beiden extremen ist pthread_once.Dies können Sie einrichten, eine Funktion, die nur einmal ausgeführt - grundsätzlich durch tun alle sperren/Sperre/etc.Tricks für dich - egal wie oft es aufgerufen wird oder auf wie viele threads aufgerufen.

Hier ist eine sehr einfache träge konstruiert singleton-getter:

Singleton *Singleton::self() {
    static Singleton instance;
    return &instance;
}

Dies ist faul, und die nächste C++ - standard (C++0x) fordert, dass es thread-sicher.In der Tat, ich glaube, dass mindestens g++ implementiert diese in eine thread-sichere Weise.Also, wenn das ist Ihre target-compiler oder wenn Sie einen compiler, der auch implementiert diese in eine thread-sichere Weise (vielleicht neuere Visual Studio-Compiler tun?Ich weiß es nicht), dann könnte dies alles sein, was Sie brauchen.

Siehe auch http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2513.html auf dieses Thema.

Sie können nicht tun es ohne statische Variablen, aber wenn Sie bereit sind, zu tolerieren, können Sie verwenden Boost.Thread für diesen Zweck.Lesen Sie die "one-time-Initialisierung" für mehr info.

Dann singleton-accessor-Funktion verwenden boost::call_once konstruieren Sie das Objekt und gibt es zurück.

Für den gcc, das ist sehr einfach:

LazyType* GetMyLazyGlobal() {
    static const LazyType* instance = new LazyType();
    return instance;
}

GCC wird, stellen Sie sicher, dass die Initialisierung ist atomar. Für VC++ ist dies nicht der Fall. :-(

One major Problem mit diesem Mechanismus ist die fehlende Prüfbarkeit:wenn Sie müssen reset die LazyType zu einem neuen zwischen-Prüfungen, oder möchten ändern Sie die LazyType* um eine MockLazyType*, werden Sie nicht in der Lage.Daher ist es normalerweise am besten, die Verwendung einer statischen mutex + static pointer.

Auch, möglicherweise eine Nebenbemerkung:Es ist am besten immer vermeiden, statische nicht-POD-Typen.(Zeiger auf "PODs" sind OK.) Die Gründe dafür sind vielfältig:wie Sie erwähnen, Initialisierung, um nicht definiert-weder ist die Reihenfolge, in der Destruktoren aufgerufen werden, obwohl.Da diese Programme werden am Ende abstürzt, wenn Sie versuchen, zu beenden;oft keine große Sache, aber manchmal ist ein showstopper, wenn der profiler die Sie verwenden möchten, erfordert einen sauberen exit.

Während diese Frage bereits beantwortet wurde, ich denke, es gibt einige andere Punkte zu erwähnen:

  • Wenn Sie möchten, lazy-Instanziierung des singleton-während Sie mit einem Zeiger auf einen dynamisch zugewiesenen Beispiel, Sie ' ll haben zu machen sicher, dass Sie sauber es bis an der richtigen Stelle.
  • Sie verwenden konnte Matt die Lösung, aber würden Sie brauchen, um eine richtige mutex/kritischen Abschnitt für die Verriegelung, und durch anklicken von "pObj == NULL" sowohl vor als auch nach der Sperre.Natürlich pObj würde auch haben werden statische ;) .Ein mutex wäre unnötig schwer in diesem Fall, würden Sie besser gehen mit einem kritischen Abschnitt.

Aber wie gesagt, man kann nicht garantiert threadsicher lazy-Initialisierung ohne mindestens eine synchronisation primitive.

Edit:Yup Derek, du hast Recht.Mein schlechtes.:)

Sie verwenden konnte Matt die Lösung, aber würden Sie brauchen, um eine richtige mutex/kritischen Abschnitt für die Verriegelung, und durch anklicken von "pObj == NULL" sowohl vor als auch nach der Sperre.Natürlich, pObj würde auch statisch sein ;) .Ein mutex wäre unnötig schwer in diesem Fall, würden Sie besser gehen mit einem kritischen Abschnitt.

OJ, das nicht funktioniert.Chris wies darauf hin, dass die double-check-locking, ist nicht garantiert zu arbeiten, in der aktuellen C++ - standard.Siehe: C++ und die Gefahren des Double-Checked-Locking

Edit:Kein problem, ABL.Es ist wirklich schön, in den Sprachen, wo es funktioniert.Ich erwarte, dass es funktionieren wird, in C++0x (obwohl ich nicht sicher bin), weil es wie eine bequeme idiom.

  1. Lesen Sie weiter, weak memory model.Es kann brechen überprüft, Schlösser und spinlocks.Intel ist stark-Speicher-Modell (noch) so auf Intel-es ist einfacher

  2. vorsichtig mit "volatile" zu vermeiden Zwischenspeichern von teilen des Objekts in Registern, ansonsten müssen Sie initialisiert das Objekt Zeiger, aber nicht das Objekt selbst, und der andere thread zum Absturz

  3. die Reihenfolge der statischen Variablen-Initialisierung versus shared-code laden ist manchmal nicht trivial.Ich habe Fälle gesehen, wenn Sie den code für die Selbstzerstörung ein Objekt wurde bereits entladen, so dass das Programm abgestürzt auf Ausfahrt

  4. solche Objekte sind schwer zu zerstören richtig

Im Allgemeinen singletons sind schwer, das richtige zu tun und schwer zu Debuggen.Es ist besser, zu vermeiden Sie Sie ganz.

Ich nehme an, sagen, tun dies nicht, weil es ist nicht sicher und wird wahrscheinlich brechen, oft mehr als nur initialisieren dieses Zeug in main() wird nicht so beliebt.

(Und ja, ich weiß, dass darauf hindeutet, dass bedeutet, Sie sollten nicht versuchen interessante Sachen in Konstruktoren von globalen Objekten.Das ist der Punkt.)

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top