2012-09-03 13 views
13

Diese Frage wurde in einem Interview gestellt. Der erste Teil war die Singleton-Klasse zu schreiben:Singleton mit Multithreads

class Singleton 
{ 
    static Singleton *singletonInstance; 
    Singleton() {} 

    public: 
    static Singleton* getSingletonInstance() 
    { 
     if(singletonInstance == null) 
     { 
      singletonInstance = new Singleton(); 
     } 
     return singletonInstance; 
    } 
}; 

Dann wurde ich gefragt, wie diese getSingletonInstance() in einer Multi-Thread-Situation zu handhaben. Ich war nicht wirklich sicher, aber ich modifizierte:

class Singleton 
{ 
    static Singleton *singletonInstance; 
    Singleton() {} 
    static mutex m_; 

    public: 
    static Singleton* getSingletonInstance() 
    { 
     m_pend(); 
     if(singletonInstance == null) 
     { 
      singletonInstance = new Singleton(); 
     } 
     return singletonInstance; 
    } 

    static void releaseSingleton() 
    { 
     m_post(); 
    } 
}; 

dann wurde mir gesagt, dass, obwohl ein Mutex erforderlich ist, anhängig und ein Mutex Posting nicht effizient ist, wie es dauert seine Zeit. Und es gibt einen besseren Weg, um mit dieser Situation umzugehen.

Kennt jemand eine bessere effiziente Möglichkeit, die Singleton-Klasse in einer Multithread-Situation zu behandeln?

+12

Kein Singleton verwenden? –

+6

Die Verwendung des globalen Status in einem Multithread-Code ist eine gute Möglichkeit, um unendliche Kopfschmerzen zu bekommen. Zweifellos im Falle von stupidgleton. –

+0

@CatPlusPlus: Es ist Einfaltspinsel. Einfaltspinsel. –

Antwort

20

In C++ 11, wird die folgende garantiert threadsicher Initialisierung auszuführen:

static Singleton* getSingletonInstance() 
{ 
    static Singleton instance; 
    return &instance; 
} 

In C++ 03 wurde ein gemeinsames Konzept zu verwenden, doppelt überprüft Verriegelung; Überprüfen eines Flags (oder des Zeigers selbst), um festzustellen, ob das Objekt möglicherweise nicht initialisiert ist, und Sperren des Mutex nur, wenn dies der Fall ist. Dies erfordert eine Art von Nicht-Standard-Art des atomaren Lesens des Zeigers (oder eines zugeordneten booleschen Flags); In vielen Implementierungen wird fälschlicherweise ein einfacher Zeiger oder bool verwendet, ohne dass sichergestellt ist, dass Änderungen an einem Prozessor in anderen sichtbar sind. Der Code könnte etwa so aussehen, obwohl ich etwas falsch verstanden mit ziemlicher Sicherheit habe:

static Singleton* getSingletonInstance() 
{ 
    if (!atomic_read(singletonInstance)) { 
     mutex_lock lock(mutex); 
     if (!atomic_read(singletonInstance)) { 
      atomic_write(singletonInstance, new Singleton); 
     } 
    } 
    return singletonInstance; 
} 

Das ist ziemlich verzwickt richtig zu machen, so schlage ich vor, dass Sie nicht stören. In C++ 11 können Sie standardmäßige atomare und Mutex-Typen verwenden, wenn Sie aus irgendeinem Grund die dynamische Zuweisung Ihres Beispiels beibehalten möchten.

Beachten Sie, dass ich nur über synchronisierte Initialisierung sprechen, nicht synchronisierten Zugriff auf das Objekt (die Ihre Version durch Sperren des Mutex im Accessor und später durch eine separate Funktion freigeben). Wenn Sie die Sperre für den sicheren Zugriff auf das Objekt selbst benötigen, können Sie natürlich nicht vermeiden, bei jedem Zugriff zu sperren.

2

Wenn Sie C++ 11 können Sie singletonInstance eine atomare Variable machen, dann eine doppelt geprüft Verriegelung:

if (singletonInstance == NULL) { 
    lock the mutex 
    if (singletonInstance == NULL) { 
     singletonInstance = new Singleton; 
    } 
    unlock the mutex 
} 
return singletonInstance; 
+9

Wenn Ihr Compiler C++ 11 korrekt implementiert, müssen Sie ihn überhaupt nicht sperren. 'static Foo & getSingleton() {statisch Foo foo {}; Rückkehr foo; } 'wird * nur unter gleichzeitiger Ausführung funktionieren. – Xeo

+0

@ Xeo - guter Punkt. Machen Sie es zu einer Antwort, und ich gebe eine Abstimmung nach oben, aber mit einem Kommentar, dass dies ineffizient sein kann, abhängig davon, wie der Compiler Sperren für statische Objekte der Funktion verwaltet (wenn es eine gemeinsame Sperre gibt, könnte es zu Konflikten kommen). –

+0

Nun, ich habe bereits eine geschrieben, aber die Frage ist nicht wirklich ein Duplikat, zumindest meiner Meinung nach: http://StackOverflow.com/a/11711991/500104. – Xeo

2

Sie sollten eigentlich die Singleton sperren, und nicht die Instanz. Wenn die Instanz Verriegelung erfordert, sollte dies durch den Anrufer (oder vielleicht durch die Instanz selbst, je nachdem, welche Art einer Schnittstelle es macht) behandelt werden

aktualisieren Beispielcode:

#include <mutex> 

class Singleton 
{ 
    static Singleton *singletonInstance; 
    Singleton() {} 
    static std::mutex m_; 

    public: 

    static Singleton* getSingletonInstance() 
    { 
     std::lock_guard<std::mutex> lock(m_); 
     if(singletonInstance == nullptr) 
     { 
      singletonInstance = new Singleton(); 
     } 
     return singletonInstance; 
    } 
} 
2

Wenn Sie POSIX-Threads verwenden, können Sie pthread_once_t und pthread_key_t Zeug verwenden, auf diese Weise können Sie vermeiden, Mutexe insgesamt zu verwenden. Zum Beispiel:

template<class T> class ThreadSingleton : private NonCopyable { 
public: 
    ThreadSingleton(); 
    ~ThreadSingleton(); 

    static T& instance(); 

private: 
    ThreadSingleton(const ThreadSingleton&); 
    const ThreadSingleton& operator=(const ThreadSingleton&) 

    static pthread_once_t once_; 
    static pthread_key_t key_; 

    static void init(void); 
    static void cleanUp(void*); 
}; 

und Umsetzung:

template<class T> pthread_once_t ThreadSingleton<T>::once_ = PTHREAD_ONCE_INIT; 
template<class T> pthread_key_t ThreadSingleton<T>::key_; 

template<class T> 
T& ThreadSingleton<T>::instance() 
{ 
    pthread_once(&once_,init); 

    T* value = (T*)pthread_getspecific(key_); 
    if(!value) 
    { 

     value = new T(); 
     pthread_setspecific(key_,value); 
    } 
    return *value; 
} 

template<class T> void ThreadSingleton<T>::cleanUp(void* data) 
{ 
    delete (T*)data; 
    pthread_setspecific(key_,0); 
} 

template<class T> void ThreadSingleton<T>::init() 
{ 
    pthread_key_create(&key_,cleanUp); 
} 
+0

Ich denke, die Frage ist, eine einzelne Instanz zu erstellen, nicht eine pro Thread. –

+0

So habe ich die Frage nicht verstanden. Ich lese es gerade noch einmal und ich denke du hast Recht. Es tut uns leid. – piokuc

12

Als @piokuc vorgeschlagen, können Sie auch einmal eine Funktion hier verwenden.Wenn Sie C++ 11:

#include <mutex> 

static void init_singleton() { 
    singletonInstance = new Singleton; 
} 
static std::once_flag singleton_flag; 

Singleton* getSingletonInstance() { 
    std::call_once(singleton_flag, init_singleton); 
    return singletonInstance; 
} 

Und ja, wird dies vernünftig funktionieren, wenn die new Singleton eine Ausnahme auslöst.

+1

Danke. Ich war mir der einmaligen Funktion nicht bewusst. – madu