双重检查锁单字在C ++ 11 [英] Double-Checked Lock Singleton in C++11

查看:94
本文介绍了双重检查锁单字在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?

>:由于约翰的答案,我想出了以下实现,应该是数据无竞争。即使内部负载可能是非原子的,我决定留下一个轻松的负载,因为它不会影响性能。相比总是具有获取存储器顺序的外部负载,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天全站免登陆