C++ 多线程单例

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12248747/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 16:05:14  来源:igfitidea点击:

Singleton with multithreads

c++multithreadingsynchronizationsingletonmutex

提问by madu

This question was asked in an interview. The first part was to write the singleton class:

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

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

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

Then I was asked how to handle this getSingletonInstance()in a multithreaded situation. I wasn't really sure, but I modified as:

然后我被问到如何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();
    }
};

Then I was told that although a mutex is required, pending and posting a mutex is not efficient as it takes time. And there is a better way to handle to this situation.

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

Does anybody know a better and more efficient way to handle the singleton class in a multithreaded situation?

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

回答by Mike Seymour

In C++11, the following is guaranteed to perform thread-safe initialisation:

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

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

In C++03, a common approach was to use double-checked locking; checking a flag (or the pointer itself) to see if the object might be uninitialised, and only locking the mutex if it might be. This requires some kind of non-standard way of atomically reading the pointer (or an associated boolean flag); many implementations incorrectly use a plain pointer or bool, with no guarantee that changes on one processor are visible on others. The code might look something like this, although I've almost certainly got something wrong:

在 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;
}

This is quite tricky to get right, so I suggest that you don't bother. In C++11, you could use standard atomic and mutex types, if for some reason you want to keep the dynamic allocation of you example.

这很难做到,所以我建议你不要打扰。在 C++11 中,如果出于某种原因要保留示例的动态分配,则可以使用标准原子和互斥类型。

Note that I'm only talking about synchronised initialisation, not synchronised access to the object (which your version provides by locking the mutex in the accessor, and releasing it later via a separate function). If you need the lock to safely access the object itself, then you obviously can't avoid locking on every access.

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

回答by Pete Becker

As @piokuc suggested, you can also use a once function here. If you have C++11:

正如@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;
}

And, yes, this will work sensibly if the new Singletonthrows an exception.

而且,是的,如果new Singleton抛出异常,这将很有效。

回答by Pete Becker

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

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

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

回答by sehe

You should actually lock the singleton, and not the instance. If the instance requires locking, that should be handled by the caller (or perhaps by the instance itself, depending on what kind of an interface it exposes)

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

Updatesample code:

更新示例代码:

#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;
    }
}

回答by piokuc

If you use POSIX threads you can use pthread_once_tand pthread_key_tstuff, this way you can avoid using mutexes altogether. For example:

如果你使用,你可以使用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*);
};

And implementation:

和实施:

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