2012-09-03 58 views
13

這個問題被問了採訪。第一部分是寫單例類:辛格爾頓與多線程

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

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

然後有人問我如何處理這種getSingletonInstance()在多線程的情況。我真的不知道,但我修改爲:

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

然後有人告訴我,儘管需要一個互斥體,等待和張貼互斥是因爲它需要時間效率不高。有一個更好的方法來處理這種情況。

有誰知道一個更好的有效的方式來處理多線程情況單身類?

+12

請不要使用singleton? –

+6

在多線程代碼中使用全局狀態是獲得無限頭痛的好方法。在愚蠢的情況下雙打。 –

+0

@CatPlusPlus:這很簡單。傻瓜。 –

回答

20

在C++ 11,下面是保證執行線程安全初始化:

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

在C++ 03,一個常用的方法是使用雙檢查鎖定;檢查標誌(或指針本身)以查看該對象是否可能未初始化,並且只有在可能的情況下才鎖定該互斥鎖。這需要某種以原子方式讀取指針的非標準方式(或相關的布爾標誌);許多實現不正確地使用普通指針或bool,但不保證其他處理器上的更改可見。該代碼可能是這個樣子,雖然我幾乎可以肯定有一些錯誤:

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

這是相當棘手的權利,所以我建議你不要打擾。在C++ 11中,如果出於某種原因想要保持示例的動態分配,則可以使用標準原子和互斥體類型。

請注意,我只談論同步初始化,不同步訪問對象(你的版本提供了鎖定在訪問互斥,並通過一個單獨的函數後釋放它)。如果您需要鎖來安全地訪問對象本身,那麼您顯然無法避免鎖定每個訪問。

2

如果你有C++ 11可以使singletonInstance原子變量,然後使用雙重檢查鎖定:

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

如果你的編譯器正確地實現了C++ 11,你根本不需要鎖定它。 'static Foo&getSingleton(){static Foo foo {};返回foo;在併發執行下''將會*工作*。 – Xeo

+0

@Xeo - 好點。讓它成爲答案,我會給它一個投票,但有一個評論,這可能是低效率的,取決於編譯器如何管理函數靜態對象的鎖(如果有一個共享鎖,你可能會爭用)。 –

+0

嗯,我已經寫了一個,但問題不是真的重複,至少在我看來:http://stackoverflow.com/a/11711991/500104。 – Xeo

2

你實際上應該鎖定單身,而不是實例。如果實例需要鎖定,應該由主叫方處理(也許是受實例本身,而取決於或什麼樣的它暴露的接口)

更新示例代碼:

#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

如果你使用POSIX線程,你可以使用pthread_once_tpthread_key_t的東西,這樣你就可以完全避免使用互斥鎖。例如:

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

與實現

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

我認爲這個問題是關於創建一個單一的實例,而不是每個線程。 –

+0

這不是我如何理解這個問題..我只是再讀一遍,我認爲你是對的。抱歉。 – piokuc

12

由於@piokuc建議,你也可以使用一個曾經在這裏發揮作用。如果你有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; 
} 

而且,是的,這將能夠有效地工作,如果new Singleton拋出異常。

+1

謝謝。我不知道曾經的功能。 – madu