C++11 中的双重检查锁单例 [英] Double-Checked Lock Singleton in C++11

查看:67
本文介绍了C++11 中的双重检查锁单例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下单例实现数据竞争是否免费?

Is the following singleton implementation data-race free?

static std::atomic<Tp *> m_instance;
...

static Tp &
instance()
{
    if (!m_instance.load(std::memory_order_relaxed))
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        if (!m_instance.load(std::memory_order_acquire))
        {
            Tp * i = new Tp;
            m_instance.store(i, std::memory_order_release);    
        }    
    }

    return * m_instance.load(std::memory_order_relaxed);
}

加载操作的std::memory_model_acquire是多余的吗?是否可以通过将它们切换到 std::memory_order_relaxed 来进一步放松加载和存储操作?在这种情况下,std::mutex 的获取/释放语义是否足以保证其正确性,或者进一步的 std::atomic_thread_fence(std::memory_order_release) 也是需要确保在轻松存储之前写入构造函数的内存吗?然而,使用fence 是否等同于使用memory_order_release 存储存储?

Is the std::memory_model_acquire of the load operation superfluous? Is it possible to further relax both load and store operations by switching them to std::memory_order_relaxed? In that case, is the acquire/release semantic of std::mutex enough to guarantee its correctness, or a further std::atomic_thread_fence(std::memory_order_release) is also required to ensure that the writes to memory of the constructor happen before the relaxed store? Yet, is the use of fence equivalent to have the store with memory_order_release?

编辑:感谢 John 的回答,我想出了以下应该没有数据竞争的实现.尽管内部负载可能根本不是原子的,但我决定留下一个宽松的负载,因为它不会影响性能.与始终使用获取内存顺序的外部负载相比,thread_local 机制将访问实例的性能提高了大约一个数量级.

EDIT: Thanks to the answer of John, I came up with the following implementation that should be data-race free. Even though the inner load could be non-atomic at all, I decided to leave a relaxed load in that it does not affect the performance. In comparison to always have an outer load with the acquire memory order, the thread_local machinery improves the performance of accessing the instance of about an order of magnitude.

static Tp &
instance()
{
    static thread_local Tp *instance;

    if (!instance && 
        !(instance = m_instance.load(std::memory_order_acquire)))
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        if (!(instance = m_instance.load(std::memory_order_relaxed)))
        {
            instance = new Tp; 
            m_instance.store(instance, std::memory_order_release);    
        }    
    }
    return *instance;
}

推荐答案

该实施并非没有种族限制.单例的原子存储虽然使用了释放语义,但只会与匹配的获取操作同步——即已经被互斥锁保护的加载操作.

That implementation is not race-free. The atomic store of the singleton, while it uses release semantics, will only synchronize with the matching acquire operation—that is, the load operation that is already guarded by the mutex.

在锁定线程完成单例初始化之前,外部松弛加载可能会读取一个非空指针.

It's possible that the outer relaxed load would read a non-null pointer before the locking thread finished initializing the singleton.

另一方面,由锁保护的获取是多余的.它将与另一个线程上具有释放语义的任何存储同步,但此时(由于互斥锁)唯一可以存储的线程是当前线程.该加载甚至不需要是原子的——另一个线程不会发生存储.

The acquire that is guarded by the lock, on the other hand, is redundant. It will synchronize with any store with release semantics on another thread, but at that point (thanks to the mutex) the only thread that can possibly store is the current thread. That load doesn't even need to be atomic—no stores can happen from another thread.

参见 Anthony Williams 的 C++0x 多线程系列.

这篇关于C++11 中的双重检查锁单例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆