在使用互斥锁时,C++单例需要内存屏障吗?

5

我从这里了解到,互斥锁也可以产生内存屏障的效果:Can mutex replace memory barriers,但我总是看到在下面的 C++ 单例模式示例中使用了一个内存屏障,这个内存屏障是否是不必要的?

Singleton* Singleton::getInstance() {
     Singleton* tmp = m_instance.load(std::memory_order_relaxed);
     std::atomic_thread_fence(std::memory_order_acquire);        
     if (tmp == nullptr) {
         std::lock_guard<std::mutex> lock(m_mutex);               // using mutex here
         tmp = m_instance.load(std::memory_order_relaxed);
         if (tmp == nullptr) {
             tmp = new Singleton;
             assert(tmp != nullptr);    
             std::atomic_thread_fence(std::memory_order_release); // using memory barrier here
             m_instance.store(tmp, std::memory_order_relaxed);
         }
     }
     return tmp;
 }

1
你根本不需要那些东西,只需按照Scott Meyer的《C++单例模式》(副本)中所述进行操作即可。它是惰性评估的,并且已经具有线程安全保证。 - πάντα ῥεῖ
谢谢,但我也想弄清楚内存屏障是否必要 @πάνταῥεῖ - woder
Pthread_mutexes通常用作C++锁的基础,将锁定和解锁操作定义为地址空间范围内的屏障;因此,在这种情况下,您不需要使用fences。您的std::lock_guard实现是否记录了该保证?标准在这个问题上似乎异常地缄默。 - mevets
你将从了解内存模型的工作原理中受益更多,而不是询问特定示例。话虽如此,通常不建议触碰除顺序一致性之外的任何原子操作。 - Passer By
“@woder:“但是我还想确定是否需要内存屏障。” 如上所述,不需要。Scott Meyer的单例模式不需要任何锁或屏障。但是,@Nathan决定重新打开并取消重复你的问题,尽管在我个人看来,你所需的所有答案都已经在那里了。” - πάντα ῥεῖ
显示剩余3条评论
1个回答

1
如果您能使用C++11,就不需要编写自己的保护程序。正如这里所提到的,所有必需的内容都已包含在C++11中。从那里复制过来:
对于单例模式,不需要双重检查锁定:
如果在变量正在初始化时并发地进入声明,则并发执行应等待初始化完成。 — § 6.7 [stmt.dcl] p4
Singleton& GetInstance() {
  static Singleton s;
  return s;
}

实现将提供内存屏障或其他机制以保护您的并发访问。因此,请像示例中所示那样保持简单!

Scott Meyer的Singleton模式使用静态初始化对象是好的和简单的,但当不同的单例实例之间存在特定的初始化顺序请求时,问题就出现了。还有另一种解决方案,使用pthread_once来创建单个实例。无论如何,我不是在寻找如何创建单个实例,只是想知道互斥锁和内存屏障的作用,感谢您的回答。 - woder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接