Gegen Internetzensur
Stoppt die Vorratsdatenspeicherung! Jetzt klicken & handeln!Willst du auch bei der Aktion teilnehmen? Hier findest du alle relevanten Infos und Materialien:
  • Flickr Photos

    www.flickr.com
7. Dez
Son

Singletons und Thread-Locks via boost

Na das war jetzt eine schwerere Geburt.

In C# ist es mit der Threadsynchronisierung ja nun wirklich nicht umständlich: Einmal:

  1. private static readonly object lockObject = new object();

und dann nur noch:

  1. lock( lockObject ) {
  2.      //… Logik hier …
  3. }

Der Lock wird automatisch aufgehoben, wenn der Scope verlassen wird.

Nun stand ich vor dem Problem, in C++ eine Singletonklasse erstellen zu wollen. Die Instanz selbst wollte ich per boost::shared_ptr speichern und von dort an durch meine GetInstance()-Funktion lediglich boost::weak_ptr an die tatsächlichen Nutzer verteilen.
Da shared_ptr allerdings nicht genullt werden können, kam eine weitere Variable hinzu, die angibt, ob die Singletoninstanz bereits erstellt wurde oder nicht.
Um nun (jedenfalls rudimentäre) Threadsicherheit zu gewährleisten, muss jedoch sichergestellt werden, dass das Testen von ‘isInitialized‘ und das tatsächliche Initialisieren atomisch passiert – ein Lock muss her. Der dafür benötigte Semaphor, bereitgestellt über einen boost::mutex, muss dann dafür allerdings ebenfalls statisch sein.

Nun nahm ich an, das Problem ließe sich über eine einfache, statische Variablenzuweisung à la
boost::mutex Singleton::lockObject = boost::mutex(); lösen – aber Pustekuchen!
Hier wird implizit der Copy-Konstruktor aufgerufen, und der ist bei boost::mutex leider privat.
Was mich nun dazu brachte, den Mutex selbst mit einem shared_ptr zu verwalten. Damit sieht das Horrorkonstrukt nun also wie folgt aus:

  1. #include <boost/thread/mutex.hpp>
  2. using namespace boost;
  3.  
  4. // Die Singleton-Klasse
  5. class Singleton {
  6. private:
  7.     // Konstruktor und Copy-Konstruktor privat
  8.     Singleton();
  9.     Singleton(const Singleton&);
  10.  
  11. public:
  12.     // Zugriff auf die Singleton-Instanz
  13.     static weak_ptr<Singleton> GetInstance();
  14.  
  15.     // Vernichtet die Singleton-Instanz
  16.     static void Destroy();
  17.  
  18. private:
  19.     // Gibt an, ob die Klasse initialisiert wurde
  20.     static bool isInitialized;
  21.  
  22.     // Das Lock-Objekt
  23.     static mutex lockObject;
  24.  
  25.     // Die Singleton-Instanz
  26.     static shared_ptr<Singleton> instance;
  27. };

Und die Implementierung:

  1. // Initialisieren der statischen Variablen
  2. bool Singleton::isInitialized = false;
  3. mutex Singleton::lockObject;
  4. shared_ptr<Singleton> Singleton::instance =
  5.                               shared_ptr<Singleton>();
  6.  
  7. Singleton::Singleton() { /* Initialisierung */ }
  8. Singleton::Singleton(const Singleton&) {}
  9.  
  10. // Liefern und, falls nötig, erzeugen der Instanz
  11. weak_ptr<Singleton> Singleton::GetInstance() {
  12.     mutex::scoped_lock lock( lockObject );
  13.     if( !isInitialized )
  14.          instance = shared_ptr<Singleton>( new Singleton() );
  15.     isInitialized = true;
  16.     return weak_ptr( instance );
  17. }
  18.  
  19. // Liefern und, falls nötig, erzeugen der Instanz
  20. void Singleton::Destroy() {
  21.     mutex::scoped_lock lock( lockObject );
  22.     if( isInitialized ) instance.reset()
  23.     isInitialized = false;
  24. }

Und, natürlich, nicht zu vergessen, dass dann noch gegen boost_thread gelinkt werden muss.

edit: Danke an Jan-Nik (siehe Kommentare) für die Rettungsaktion

9 Antworten zu „Singletons und Thread-Locks via boost”

  1. #1~Jan-Nik

    Mach den Mutex einfach als eine Membervariable.

  2. #2~Markus

    Geht ja nicht, weil ich ihn ja checken muss, bevor die Singletoninstanz erzeugt wurde. (Oder steh ich auf’m Schlauch?)

  3. #3~Jan-Nik

    Achja stimmt. Aber der SmartPointer ist nicht notwendig, du kannst einfach ne statische Variable machen glaub ich.

  4. #4~Markus

    Hm, an der Stelle bin ich dann an folgendem Problem gescheitert:

    boost::mutex Singleton::lockObject = boost::mutex();
    

    geht nicht, weil boost::mutex von boost::noncopyable ableitet (privater copy-Konstruktor) und damit rhs nicht nach lhs zugewiesen werden kann.

    Und:

    boost::mutex Singleton::lockObject();
    

    geht auch nicht, weil er es dann als Funktion interpretiert. :/

    Der Smartpointer hab ich dann nur noch als letzten Ausweg gesehen, mich nicht auch noch um ein deleten kümmern zu müssen – war mir so schon zu anstrengend! :D Aber irgendwie meine ich trotzdem, das muss auch anders gehen.

    (Danke übrigens! Wie hast du eigentlich hergefunden?)

  5. #5~Jan-Nik

    Und so?

    boost::mutex Singleton::lockObject;

    Bin über Google hierher gelangt als ich mich über boost::mutex informieren wollte.

  6. #6~Markus

    Jap, das war’s.
    Ich glaube, ich habe so viel mit C# gearbeitet in der letzten Zeit, dass alles ohne Konstruktor irgendwie unheimlich auf mich wirkt.
    Ich ändere es mal oben im Text – danke dir!

  7. #7~work and travel

    Why this web site do not have other languages support?

  8. #8~Markus

    Why don’t you ask in German? :P :)
    Really, the code speaks for itself if you already know how thread synchronization works but search for an implementation with boost. Feel free to ask, though!

  9. #9~peterchen

    Mit

    boost::mutex Singleton::lockObject;

    rennt man leider früher oder später in das Problem, das die Initialisierungsreihenfolge zwischen Übersetzungseinheiten undefiniert ist (http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12).

    Das ist kein problem solange man nur “fast-primitive” statics anlegt – solche ohne Referenzen auf andere Klassen die in einer statischen Instanz referenziert werden könnten. Ansonsten scheint mir lock + synchronisation wohl die bessere Lösung.