双重检查locking单身人士在C + + 11
下面的单例实现数据竞赛是免费的吗?
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)
来确保构造函数的内存写入发生在轻松的商店? 然而,使用围栏相当于有memory_order_release
商店吗?
编辑 :感谢约翰的答案,我想出了下面的应该是免费的数据竞赛的实现。 即使内部的负载可能是非primefaces的,我决定放松一下,不影响性能。 相比之下,总是有获取内存顺序的外部负载,thread_local机制提高了访问实例大约一个数量级的性能。
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; }
这个实现不是无竞赛的。 单例的primefaces存储在使用释放语义的同时,只会与匹配的获取操作(即已经被互斥锁保护的加载操作)同步。
在locking线程完成初始化单例之前,外部放松的负载可能会读取一个非空指针。
另一方面,锁的守护是多余的。 它将与任何商店同步在另一个线程释放语义,但在这一点上(感谢互斥)唯一可能存储的线程是当前线程。 这个负载甚至不需要是primefaces的 – 任何商店都不能从另一个线程发生。
参见C ++ 0xmultithreading的Anthony Williams系列 。
我认为这是一个很好的问题,John Calsbeek有正确的答案。
然而,要明确一个懒惰的单身人士是最好的使用经典的迈尔斯单身人士。 它在C ++ 11中保证了正确的语义。
§6.7.4
…如果控制在初始化variables时同时进入声明,则并发执行应等待初始化完成。 …
Meyer的单例是首选,因为编译器可以积极地优化并发代码。 如果必须保留std::mutex
的语义,编译器会受到更多的限制。 此外,迈耶的单身是2线 ,几乎不可能出错。
这是一个迈尔的单身人士的典型例子。 简单,优雅,并破坏在C + + 03。 但简单,优雅,function强大的C + + 11。
class Foo { public: static Foo& instance( void ) { static Foo s_instance; return s_instance; } };
另请参阅call_once 。 如果你以前使用单例做某件事,但实际上并没有使用返回的对象,call_once可能是更好的解决scheme。 对于一个普通的单身你可以做call_once来设置一个(全局?)variables,然后返回该variables…
简化:
template< class Function, class... Args> void call_once( std::once_flag& flag, Function&& f, Args&& args...);
-
执行正好一个函数中的一个函数,作为f传递给组(相同标志对象)中的调用。
-
在上述执行所选function成功完成之前,组中的调用不会返回