以下单例实现线程安全吗?

时间:2015-11-10 09:59:52

标签: c++ multithreading c++11 thread-safety

以下单例实现线程安全吗? :: Instance方法应该是正确的,Dispose是我自己的创建,所以要确保我没有忽略任何东西。

std::atomic<S *> S::_instance;
std::mutex S::_singleton_mutex;

S& S::Instance()
{
    using namespace std;
    S * tmp = _instance.load(memory_order_relaxed);
    atomic_thread_fence(memory_order_acquire);
    if (tmp == nullptr)
    {
        lock_guard<mutex> l(_singleton_mutex);
        tmp = _instance.load(memory_order_relaxed);
        if (tmp == nullptr)
        {
            tmp = new S();
            atomic_thread_fence(memory_order_release);
            _instance.store(tmp, memory_order_relaxed);
    }
    return *tmp;
}

void S::Dispose()
{
    using namespace std;
    S * tmp = _instance.load(memory_order_relaxed);
    atomic_thread_fence(memory_order_acquire);
    if (tmp != nullptr)
    {
        lock_guard<mutex> l(_singleton_mutex);
        tmp = _instance.load(memory_order_relaxed);
        if (tmp != nullptr)
        {
            atomic_thread_fence(memory_order_release);
            _instance.store(nullptr, memory_order_relaxed);
            delete tmp;
        }
    }
}

2 个答案:

答案 0 :(得分:2)

解决方案是:是的,看起来不错。

更多信息:

如果您可能在短时间内有两个实例,第二个实例将被立即销毁,您可以摆脱互斥锁:

std::atomic<S *> S::_instance;

S& S::Instance()
{
    using namespace std;
    auto tmp = _instance.load(memory_order_relaxed);
    if (tmp == nullptr)
    {
        auto tmp2 = new S();
        if( !_instance.compare_exchange_strong(tmp, tmp2) )
            delete tmp2;
    }
    return *tmp;
}

void S::Dispose()
{
    using namespace std;
    auto tmp = _instance.load(memory_order_relaxed);
    if (tmp != nullptr)
    {
        if( _instance.compare_exchange_strong(tmp, nullptr) )
            delete tmp;
    }
}

当两个线程同时启动Instance()时,两者都会看到nullptr并创建一个新的S。只有其中一个将成功替换实例指针,而另一个将立即删除新的S.

无论如何,您可能更喜欢使用Scott Meyers单身,但它没有提供处理对象的方法:

S& S::Instance() 
{
    // This instance will be created at first call - thread safe.
    // it lives until the program terminates.
    static Singleton instance;
    return instance;
}

这是最优雅,最小的代码和线程安全,顺便说一句。

答案 1 :(得分:1)

如其他答案所述,实施似乎很好。但是,存在一个概念性问题:用户和实例处理者之间的竞争条件。

Thread A: var i = s::Instance();
Thread B: s::Dispose();
Thread A: i.doSth();

可能存在一些用例,你可以保证这种情况永远不会发生;除此以外, 引用计数可能是解决此问题的方法。