So implementieren Sie Multithread-sicheren Singleton in C ++ 11 ohne Verwendung von <mutex>


71

Jetzt, da C ++ 11 Multithreading hat, habe ich mich gefragt, wie man faul initialisierte Singleton ohne Verwendung von Mutexen (aus Perf-Gründen) richtig implementiert. Ich habe mir das ausgedacht, aber ich bin nicht wirklich gut darin, sperrfreien Code zu schreiben, also suche ich nach besseren Lösungen.

// ConsoleApplication1.cpp : Defines the entry point for the console application.
//
# include <atomic>
# include <thread>
# include <string>
# include <iostream>
using namespace std;
class Singleton
{

public:
    Singleton()
    {
    }
static  bool isInitialized()
    {
        return (flag==2);
    }
static  bool initizalize(const string& name_)
    {
        if (flag==2)
            return false;// already initialized
        if (flag==1)
            return false;//somebody else is initializing
        if (flag==0)
        {
            int exp=0;
            int desr=1;
            //bool atomic_compare_exchange_strong(std::atomic<T>* obj, T* exp, T desr)
            bool willInitialize=std::atomic_compare_exchange_strong(&flag, &exp, desr);
            if (! willInitialize)
            {
                //some other thread CASed before us
                std::cout<<"somebody else CASed at aprox same time"<< endl;
                return false;
            }
            else 
            {
                initialize_impl(name_);
                assert(flag==1);
                flag=2;
                return true;
            }
        }
    }
static void clear()
{
    name.clear();
    flag=0;
}
private:
static  void initialize_impl(const string& name_)
{
        name=name_;
}
static  atomic<int> flag;
static  string name;
};
atomic<int> Singleton::flag=0;
string Singleton::name;
void myThreadFunction()
{
    Singleton s;
    bool initializedByMe =s.initizalize("1701");
    if (initializedByMe)
        s.clear();

}
int main()
{
    while (true)
    {
        std::thread t1(myThreadFunction);
        std::thread t2(myThreadFunction);
        t1.join();
        t2.join();
    }
    return 0;
}

Beachten Sie, dass clear()nur zu Testzwecken echte Singleton diese Funktion nicht haben würden.


Antworten:


153

C ++ 11 macht manuelles Sperren überflüssig. Die gleichzeitige Ausführung muss warten, wenn eine statische lokale Variable bereits initialisiert wird.

§6.7 [stmt.dcl] p4

Wenn die Steuerung gleichzeitig in die Deklaration eingibt, während die Variable initialisiert wird, muss die gleichzeitige Ausführung auf den Abschluss der Initialisierung warten.

Als solche haben einfach eine staticFunktion wie diese:

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

Dies funktioniert in C ++ 11 einwandfrei (solange der Compiler diesen Teil des Standards natürlich ordnungsgemäß implementiert).


Die wirklich richtige Antwort ist natürlich, keinen Singleton-Punkt zu verwenden.


4
Natürlich gibt es keine Garantie dafür, dass die Implementierung keine Mutices verwendet, um dieses Verhalten sicherzustellen. Daher ist das "ohne Mutices aus Leistungsgründen" möglicherweise nicht zufriedenstellend (ob die Singleton-Initialisierung der richtige Ort ist, um sich darüber Sorgen zu machen, ist natürlich umstritten )
Grizzly

4
@Grizzly: Nun, es "nicht" verwendet <mutex>. ;)
Xeo

7
+1, wenn auch nur für Of course, the real correct answer is to not use a singleton, period... :-) ...
paercebal

3
"Of course, the real correct answer is to not use a singleton, period."- Ich muss nicht zustimmen, Singletons haben einen Zweck, sie werden häufig überbeansprucht und missbraucht, aber sie haben einen Zweck.
Leandros

2
@Leandros Und welches wäre das?
Schuh

44

Für mich ist der beste Weg, einen Singleton mit C ++ 11 zu implementieren:

class Singleton {
 public:
  static Singleton& Instance() {
    // Since it's a static variable, if the class has already been created,
    // it won't be created again.
    // And it **is** thread-safe in C++11.
    static Singleton myInstance;

    // Return a reference to our instance.
    return myInstance;
  }

  // delete copy and move constructors and assign operators
  Singleton(Singleton const&) = delete;             // Copy construct
  Singleton(Singleton&&) = delete;                  // Move construct
  Singleton& operator=(Singleton const&) = delete;  // Copy assign
  Singleton& operator=(Singleton &&) = delete;      // Move assign

  // Any other public methods.

 protected:
  Singleton() {
    // Constructor code goes here.
  }

  ~Singleton() {
    // Destructor code goes here.
  }

 // And any other protected methods.
}

befindet sich ~ Singleton () im Schutzgebiet? Ist es zugänglich?
Snb

3
static Singleton myInstance;sollte in der Implementierung nicht der Header definiert werden.
Jeremy

Warum wurde der Konstruktor nicht privat, sondern geschützt? Abgeleitete Klassen haben Zugriff auf den Singleton-Klassenkonstruktor und erstellen ein weiteres Singleton-Objekt.
bigdata2

4

IMHO ist der beste Weg, Singletons zu implementieren, ein "Double-Check-Single-Lock" -Muster, das Sie portabel in C ++ 11 implementieren können: Double-Checked Locking ist in C ++ 11 behoben behoben. Erstellter Fall, der nur einen einzigen Zeigervergleich erfordert und im ersten Anwendungsfall sicher ist.

Wie in der vorherigen Antwort erwähnt, garantiert C ++ 11 die Sicherheit der Konstruktionsreihenfolge für statische lokale Variablen. Ist die Initialisierung lokaler statischer Variablen in C ++ 11 threadsicher? Sie sind also sicher, wenn Sie dieses Muster verwenden. Visual Studio 2013 unterstützt dies jedoch noch nicht :-( Siehe die Zeile "Magic Statics" auf dieser Seite . Wenn Sie also VS2013 verwenden, müssen Sie dies weiterhin selbst tun.

Leider ist nichts jemals einfach. Der Beispielcode, auf den für das obige Muster verwiesen wird, kann nicht von der CRT-Initialisierung aufgerufen werden, da der statische std :: mutex einen Konstruktor hat und daher nicht garantiert werden kann, dass er vor dem ersten Aufruf initialisiert wird, um den Singleton zu erhalten, wenn der Aufruf ein Side- ist. Effekt der CRT-Initialisierung. Um dies zu umgehen , müssen Sie keinen Mutex verwenden, sondern einen Zeiger auf Mutex, der garantiert vor Beginn der CRT-Initialisierung auf Null initialisiert wird. Dann müssten Sie std :: atomic :: compare_exchange_strong verwenden, um den Mutex zu erstellen und zu verwenden.

Ich gehe davon aus, dass die C ++ 11-Thread-sichere Semantik für die lokale statische Initialisierung auch dann funktioniert, wenn sie während der CRT-Initialisierung aufgerufen wird.

Wenn Sie also die threadsichere lokal-statische Initialisierungssemantik von C ++ 11 zur Verfügung haben, verwenden Sie diese. Wenn nicht, müssen Sie noch einige Arbeiten erledigen, auch wenn Ihr Singleton während der CRT-Initialisierung threadsicher sein soll.


3
template<class T> 
class Resource
{
    Resource<T>(const Resource<T>&) = delete;
    Resource<T>& operator=(const Resource<T>&) = delete;
    static unique_ptr<Resource<T>> m_ins;
    static once_flag m_once;
    Resource<T>() = default;

public : 
    virtual ~Resource<T>() = default;
    static Resource<T>& getInstance() {
        std::call_once(m_once, []() {
            m_ins.reset(new Resource<T>);
        });
        return *m_ins.get();
    }
};

1
Eines ist mir nicht klar. Warum deklarieren Sie Destruktor als virtuell? Wie würde man von Singleton erben, wenn der Konstruktor privat ist?
Irsis

2

Es ist schwierig, Ihren Ansatz zu lesen, da Sie den Code nicht wie beabsichtigt verwenden. Das heißt, das übliche Muster für einen Singleton lautet, instance()die einzelne Instanz abzurufen und dann zu verwenden (auch, wenn Sie wirklich einen Singleton möchten, nein Konstruktor sollte öffentlich sein).

Auf jeden Fall denke ich nicht, dass Ihr Ansatz sicher ist. Bedenken Sie, dass zwei Threads versuchen, den Singleton zu erfassen. Der erste, der das Flag aktualisiert, ist der einzige, der initialisiert wird, aber die initializeFunktion wird beim zweiten früh beendet Eins, und dieser Thread verwendet möglicherweise den Singleton, bevor der erste Thread die Initialisierung abgeschlossen hat.

Die Semantik von dir initializeist kaputt. Wenn Sie versuchen, das Verhalten der Funktion zu beschreiben / zu dokumentieren, werden Sie Spaß haben und am Ende eher die Implementierung als eine einfache Operation beschreiben. Die Dokumentation ist normalerweise eine einfache Möglichkeit, ein Design / einen Algorithmus zu überprüfen: Wenn Sie am Ende beschreiben, wie und nicht was , sollten Sie zum Design zurückkehren. Insbesondere gibt es keine Garantie dafür, dass initializedas Objekt nach Abschluss tatsächlich initialisiert wurde (nur wenn der zurückgegebene Wert ist trueund manchmal false, aber nicht immer).


Code ist hässlich, aber bitte nicht, dass initialize bool zurückgibt. So weiß der Anrufer, ob es initialisiert wurde oder nicht, wenn nicht, kann er auf isInitialized
NoSenseEtAl

2
#pragma once

#include <memory>
#include <mutex>

namespace utils
{

template<typename T>
class Singleton
{
private:
    Singleton<T>(const Singleton<T>&) = delete;
    Singleton<T>& operator = (const Singleton<T>&) = delete;

    Singleton<T>() = default;

    static std::unique_ptr<T> m_instance;
    static std::once_flag m_once;

public:
    virtual ~Singleton<T>() = default;

    static T* getInstance()
    {
        std::call_once(m_once, []() {
            m_instance.reset(new T);
        });
        return m_instance.get();
    }

    template<typename... Args>
    static T* getInstance2nd(Args&& ...args)
    {
        std::call_once(m_once, [&]() {
            m_instance.reset(new T(std::forward<Args>(args)...));
        });
        return m_instance.get();
    }
};

template<typename T> std::unique_ptr<T> Singleton<T>::m_instance;
template<typename T> std::once_flag Singleton<T>::m_once;

}

Diese Version ist gleichzeitig kostenlos, wenn nicht garantiert wird, dass der C ++ 11-Standard zu 100% unterstützt wird. Es bietet auch eine flexible Möglichkeit, die "eigene" Instanz zu instanziieren. Selbst wenn das magische statische Wort in C ++ 11 und höher ausreicht, muss der Entwickler möglicherweise viel mehr Kontrolle über die Instanzerstellung erlangen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.