2012-09-03 17 views
13

To pytanie zadano w wywiadzie. Pierwsza część była napisać singleton Klasa:Singleton z wielowątkowymi

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

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

Następnie poproszono mnie, jak obsługiwać ten getSingletonInstance() w wielowątkowym sytuacji. Nie byłem pewien, ale zmodyfikowany jako:

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(); 
    } 
}; 

następnie powiedziano mi, że chociaż wymagana jest mutex czasu i zamieszczając mutex nie jest skuteczny, gdyż wymaga czasu. I jest lepszy sposób radzenia sobie z tą sytuacją.

Czy ktoś zna lepszy sposób radzenia sobie z klasą singleton w sytuacji wielowątkowej?

+12

Nie używać w ogóle singleton? –

+6

Używanie stanu globalnego w kodzie wielowątkowym to dobry sposób na uzyskanie nieskończonych bólów głowy. Zupełnie tak w przypadku głupota. –

+0

@CatPlusPlus: To jest prostota. Prostak. –

Odpowiedz

20

w C++ 11 następuje wątek wykonania gwarantuje bezpieczne inicjalizacji:

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

C++ 03 wspólny sposobem było sprawdzane dwukrotnie blokującego; sprawdzanie flagi (lub samego wskaźnika), aby zobaczyć, czy obiekt może nie zostać zainicjowany, i tylko blokowanie muteksu, jeśli to możliwe. Wymaga to pewnego rodzaju niestandardowego sposobu atomowego odczytu wskaźnika (lub powiązanej flagi boolowskiej); wiele implementacji nieprawidłowo używa zwykłego wskaźnika lub bool, bez gwarancji, że zmiany na jednym procesorze są widoczne na innych. Kod może wyglądać tak, chociaż ja prawie na pewno coś źle:

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

Jest to dość trudne, aby uzyskać prawo, więc sugeruję, że nie przeszkadza. W C++ 11 możesz użyć standardowych typów atomów i muteksów, jeśli z jakiegoś powodu chcesz zachować alokację dynamiczną na przykład.

Zauważ, że mówię tylko o inicjalizacji zsynchronizowanej, niezsynchronizowanym dostępie do obiektu (zapewniana przez twoją wersję przez zablokowanie muteksu w akcesorium i zwolnienie go później za pomocą oddzielnej funkcji). Jeśli potrzebujesz blokady, aby bezpiecznie uzyskać dostęp do samego obiektu, oczywiście nie można uniknąć blokowania przy każdym dostępie.

2

Jeśli masz C++ 11 można dokonać singletonInstance zmienną atomową, a następnie użyć dwukrotnie sprawdzane Lock:

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

Jeśli twój kompilator poprawnie implementuje C++ 11, nie musisz go wcale blokować. 'static Foo & getSingleton() {static Foo foo {}; return foo; } 'will * just works * under concurrent execution. – Xeo

+0

@Xeo - dobry punkt. Zrób odpowiedź, a ja oddam głos, ale z komentarzem, że może to być nieefektywne, w zależności od tego, w jaki sposób kompilator zarządza blokadami dla obiektów statycznych funkcji (jeśli istnieje jeden wspólny blokad, możesz uzyskać rywalizację). –

+0

Cóż, już napisałem, ale pytanie nie jest tak naprawdę duplikatem, przynajmniej moim zdaniem: http://stackoverflow.com/a/11711991/500104. – Xeo

2

Należy faktycznie zablokować Singleton, a nie instancji. Jeśli instancja wymaga blokowania, które powinny być obsługiwane przez osoby dzwoniącej (lub być może przez samą przykład, w zależności od rodzaju interfejsu naraża) kodu

Aktualizacja próbki:

#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

Jeśli używasz wątków POSIX, możesz używać rzeczy o numerach pthread_once_t i pthread_key_t, dzięki czemu możesz zupełnie uniknąć używania muteksów. Na przykład:

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*); 
}; 

i realizacja:

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

Myślę, że pytanie dotyczy tworzenia pojedynczej instancji, a nie jednej na wątek. –

+0

To nie jest tak, jak rozumiem pytanie. Po prostu czytam to ponownie i myślę, że masz rację. Przepraszam. – piokuc

12

Jak @piokuc zasugerował, można również użyć raz funkcjonowała tutaj.Jeśli masz 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; 
} 

I tak, to będzie działać rozsądnie, jeśli new Singleton zgłasza wyjątek.

+1

Dziękuję. Nie wiedziałem o raz funkcji. – madu