25

这个问题是在一次采访中被问到的。第一部分是编写单例类:

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

然后有人告诉我,虽然需要互斥锁,但挂起和发布互斥锁效率不高,因为这需要时间。并且有更好的方法来处理这种情况。

有人知道在多线程情况下处理单例类的更好更有效的方法吗?

4

5 回答 5

32

在 C++11 中,保证执行线程安全初始化:

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

在 C++03 中,一种常见的方法是使用双重检查锁定;检查标志(或指针本身)以查看对象是否可能未初始化,如果可能,则仅锁定互斥锁。这需要某种非标准的原子读取指针(或相关的布尔标志)的方式;许多实现错误地使用普通指针 or 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 中,您可以使用标准的原子和互斥类型,如果出于某种原因您想要保持示例的动态分配。

请注意,我只是在谈论同步初始化,而不是对对象的同步访问(您的版本通过将互斥锁锁定在访问器中并稍后通过单独的函数释放它来提供)。如果您需要锁来安全地访问对象本身,那么您显然无法避免每次访问都锁定。

于 2012-09-03T13:46:25.157 回答
15

正如@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而且,是的,如果抛出异常,这将有效。

于 2012-09-03T15:56:06.773 回答
3

If you have C++11 you can make singletonInstance an atomic variable, then use a double-checked lock:

if (singletonInstance == NULL) {
    lock the mutex
    if (singletonInstance == NULL) {
        singletonInstance = new Singleton;
    }
    unlock the mutex
}
return singletonInstance;
于 2012-09-03T13:26:53.163 回答
3

如果您使用 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);
}
于 2012-09-03T13:47:09.283 回答
3

您实际上应该锁定单例,而不是实例。如果实例需要锁定,则应由调用者处理(或者可能由实例本身处理,具体取决于它公开的接口类型)

更新示例代码:

#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;
    }
}
于 2012-09-03T13:32:41.207 回答