单线程与多线程

时间:2012-09-03 13:21:59

标签: c++ multithreading synchronization singleton mutex

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

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

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

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

5 个答案:

答案 0 :(得分:29)

在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中,您可以使用标准的原子和互斥类型,如果由于某种原因您希望保持动态分配示例。

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

答案 1 :(得分:14)

正如@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抛出异常,这将合理地工作。

答案 2 :(得分:3)

如果你有C ++ 11,你可以使singletonInstance成为原子变量,然后使用双重检查锁:

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

答案 3 :(得分: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;
    }
}

答案 4 :(得分: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);
}
相关问题