wie schreibe ich ein singleton für multithreads



  • Zu dem Thema wärmstens zu empfehlen:
    C++ and the perils of double checked locking



  • C++ kennt keine Threads. Da musst du schon deine Umgebung konsultieren. Bei Singletons, die selbst threadsicher sind, ist nur das Erzeugen kritisch. Da empfiehlt es sich, dies in der Phase des Programms zu machen, in der es noch nicht mehrere Threads gibt. Dann musss man sich um ein Locking der Singletonerzeugung keine Gedanken machen.



  • Removed.



  • NickHappy schrieb:

    Die Variante hat evtl. den Nachteil, dass immer eine Instanz erzeugt wird, egal, ob sie verwendet wird oder nicht.

    Stimmt IMHO so nicht, da statische Objekte auch erst konstruiert werden wenn die Funktion das erste mal aufgerufen wird. Mag allerdings sein dass der Speicher schon vorher reserviert wird.

    Was mich in dem Zusammenhang allerdings interessieren würde: Was, wenn beim Aufruf der Methode aus einem Thread B heraus gerade der Thread A die Konstruktion ausgelöst, aber noch nicht abgeschlossen hat?



  • LordJaxom schrieb:

    NickHappy schrieb:

    Die Variante hat evtl. den Nachteil, dass immer eine Instanz erzeugt wird, egal, ob sie verwendet wird oder nicht.

    Stimmt IMHO so nicht, da statische Objekte auch erst konstruiert werden wenn die Funktion das erste mal aufgerufen wird. Mag allerdings sein dass der Speicher schon vorher reserviert wird.

    Was mich in dem Zusammenhang allerdings interessieren würde: Was, wenn beim Aufruf der Methode aus einem Thread B heraus gerade der Thread A die Konstruktion ausgelöst, aber noch nicht abgeschlossen hat?

    Das muss man verhindern. Zum Beispiel indem man die Methode einmal Singlethreaded aufruft.



  • Dachte ich mir. C++ kennt halt keine Threads 😉



  • NickHappy schrieb:

    Wenn Du die GetInstance-Methode so implementierst, brauchst du Double-Locking nicht. Die Variante hat evtl. den Nachteil, dass immer eine Instanz erzeugt wird, egal, ob sie verwendet wird oder nicht. Bei sehr großen Objekten, die nur selten benötigt werden ist Double-Locking evtl. angebrachter.

    CMyClass * CMyClass::GetInstance()
    {
      static CMyClass instance;
      return &instance;  
    }
    

    Aber Obacht! Wenn mehrere Threads auf das Objekt zugreifen und mindestens ein Thread das Objekt verändert, dann musst du Locking einsetzen. Das geht unter Windows mit CriticalSections am einfachsten.

    Diese Variante ist NICHT threadsafe, zumindest nicht mit den Compilern die ich kenne. Die erzeugen intern nämlich ein "init-flag" um bei jedem Aufruf von GetInstance() zu prüfen ob "instance" schon initialisiert wurde. Und dieser Test wird nicht threadsafe gemacht.

    Threadsafe geht es z.B. mit "do_once", was wiederum z.B. in Boost.Thread oder auch PTHREADS implementiert ist.



  • hustbaer hat recht! Der Code ist tatsächlich nicht threadsafe.

    CMyClass * CMyClass::GetInstance()
    {
      static CMyClass instance;
      return &instance;  
    }
    

    Ich dachte bis eben, dass alle globalen Varibalen und alle statischen Varibalen vor Programmstart (in undefinierter Reihenfolge) initialisiert werden. Das stimmt so jedoch nicht uneingeschränkt. Entschuldigung.

    Habs noch mal nachgeschlagen. So gehts (mit Double-Locking)

    static Singleton* getInstance()
    {
      if ( !pInstance ) 
      {
        taskLock();
        if ( !pInstance )
          pInstance = new Singleton;
        taskUnlock();
      }
      return pInstance ;
    }
    


  • @NickHappy:
    Mit Einschränkungen - dazu siehe den Artikel den ich eingangs verlinkt habe 😉



  • NickHappy schrieb:

    hustbaer hat recht! Der Code ist tatsächlich nicht threadsafe.

    CMyClass * CMyClass::GetInstance()
    {
      static CMyClass instance;
      return &instance;  
    }
    

    Ich dachte bis eben, dass alle globalen Varibalen und alle statischen Varibalen vor Programmstart (in undefinierter Reihenfolge) initialisiert werden. Das stimmt so jedoch nicht uneingeschränkt. Entschuldigung.

    Habs noch mal nachgeschlagen. So gehts (mit Double-Locking)

    static Singleton* getInstance()
    {
      if ( !pInstance ) 
      {
        taskLock();
        if ( !pInstance )
          pInstance = new Singleton;
        taskUnlock();
      }
      return pInstance ;
    }
    

    Lies das vom LordJaxom verlinkte Paper vom Scott Meyers & Andrei Alexandrescu. Du bist gerade dabei die übliche Kette "hey, so gehts", "nö, geht doch nicht", "aber so gehts", "nö, geht auch nicht" etc. durchzuackern die da drinnen beschrieben ist:

    http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf

    Multithreadind ist die Hölle, und bestraft wird (fast) jeder der sich für schlau hält 😉



  • hustbaer schrieb:

    Multithreadind ist die Hölle, und bestraft wird (fast) jeder der sich für schlau hält 😉

    Ist mehr ein C++ Compiler Problem, als ein Multithreading Problem.

    Of critical importance is the observation that compilers are not constrained
    to perform these steps in this order! In particular, compilers are sometimes
    allowed to swap steps 2 and 3.



  • hustbaer schrieb:

    Multithreadind ist die Hölle, und bestraft wird (fast) jeder der sich für schlau hält.

    ... weil er OOP, design patterns und C++ mit ins spiel bringt 😃



  • vista schrieb:

    hustbaer schrieb:

    Multithreadind ist die Hölle, und bestraft wird (fast) jeder der sich für schlau hält.

    ... weil er OOP, design patterns und C++ mit ins spiel bringt 😃

    Nein, weil er glaubt einfache, funktionierende Lösungen optimieren zu können ohne sie dabei kaputtzumachen. Lösungen wie z.B.:

    Foo& Foo::GetInstance()
    {
        ScopedLock lock(s_mutex);
        if (!s_instance)
            s_instance = new Foo();
        return *s_instance;
    }
    

    @Leser: ich würde nicht sagen dass es ein C++ Problem ist. Wer mit Threads arbeitet und anfängt diverse Dinge zu "optimieren" kann auch in Java oder .NET Sprachen schnell über seine eigenen "Schlauheit" stolpern.



  • Den zuerst geposteten Code habe ich nicht erfunden (oder optimiert), sondern er nennt sich "Meyer's Singleton" und ist demzufolge von Meyer.

    Du bist gerade dabei die übliche Kette "hey, so gehts", "nö, geht doch nicht", "aber so gehts", "nö, geht auch nicht" etc. durchzuackern die da drinnen beschrieben ist...

    Unsinn.

    Multithreadind ist die Hölle, und bestraft wird (fast) jeder der sich für schlau hält.

    Ich habe meinen Irrtum zugegeben. Nun weide dich nicht an den Fehlern anderer, sondern sieh zu, dass du selber ja nie einen (Fehler) machst. *g*



  • NickHappy, ich habe selbst schon genug Fehler gemacht, und es kommen wohl laufend welche dazu. Darum geht's mir aber nicht. Vielmehr darum dass dein 2. Vorschlag den du mit "Habs noch mal nachgeschlagen. So gehts (mit Double-Locking)" angepriesen hast wieder falsch ist (BTW: du meinst wohl "double checked locking" -- probier den Suchbegriff mal in Google, da findest du haufenweise zu dem Thema, u.A. an Stelle ~6 das hier verlinkte Paper).

    Und dass du diesen 2. Code auch nicht selbst erdacht hast, sondern irgendwo nachgeschlagen, beweist nur wieviel falscher Code "da draussen" zu finden ist. Tragisch aber wahr. Und genau deswegen reite ich auch so auf dem Thema herum.

    Und noch einmal: bitte lies das hier: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
    Auf Seite 3 ("3 The Double-Checked Locking Pattern") steht GENAU der Code den du als 2. gepostet hast, und im weiteren dann auch haarklein erklärt warum er falsch ist.



  • hustbaer schrieb:

    @Leser: ich würde nicht sagen dass es ein C++ Problem ist. Wer mit Threads arbeitet und anfängt diverse Dinge zu "optimieren" kann auch in Java oder .NET Sprachen schnell über seine eigenen "Schlauheit" stolpern.

    Im allgemeinen ja, aber hier kommen die Probleme schon auch von C++. In Java wird die instnz nicht != null sein, obwohl sie noch garnicht vollstndig erzeugt ist.



  • Den Code habe ich aus meiner "Deeply Embedded Vorlesung". Werde meinen Prof. nochmal drauf aufmerksam machen, dass der Code u.U. Probleme machen kann.



  • Leser schrieb:

    ...In Java wird die instnz nicht != null sein, obwohl sie noch garnicht vollstndig erzeugt ist.

    Eine gültige Referenz auf ein Objekt, das noch nicht vollständig erzeugt ist ?
    Das klingt mir nach einem echten Problem !!

    Gruß,

    Simon2.



  • Der Pointer pInstance (bzw. in Java halt Referenz) kann sowohl in C++ als auch in Java != 0 sein bevor das Objekt fertig konstruiert ist, wenn man keine explizite Memory Barrier reinschreibt.
    In Java 1.5 bzw. auch mit MSVC8 geht es wenn man pInstance volatile macht, mit "normalen" C++ Compilern bzw. vor Java 1.5 darf man sich allerdings nicht darauf verlassen...


Anmelden zum Antworten