Meyers的Singleton模式线程是否安全?

以下是Singleton (Meyers'Singleton)线程安全的使用延迟初始化的实现吗?

 static Singleton& instance() { static Singleton s; return s; } 

如果没有,为什么以及如何使线程安全?

在C ++ 11中 ,它是线程安全的。 根据标准 , §6.7 [stmt.dcl] p4

如果控制在variables初始化时同时进入声明,则并发执行应等待初始化完成。

GCC和VS支持该function( dynamic初始化和并发性破坏 ,也称为MSDN上的Magic Statics )如下:

  • Visual Studio:自Visual Studio 2015以来受支持
  • GCC:自GCC 4.3以来支持

感谢@Mankarse和@olen_gam的评论。


在C ++ 03中 ,这个代码不是线程安全的。 有一篇由Meyers撰写的名为“C ++和双重locking的危险”的文章 ,讨论了模式的线程安全实现,结论或多或less(在C ++ 03中)完全locking了实例化方法基本上是确保在所有平台上正确并发的最简单的方法,而大多数forms的双重检查locking模式变体可能遭受某些体系结构上的竞争条件的影响 ,除非指令在策略上将内存屏障交织在一起。

为了回答你为什么不是线程安全的问题,这不是因为第一次调用instance()必须调用Singleton s构造函数。 要成为线程安全的,这必须发生在关键部分,但是在标准中没有要求采用关键部分(迄今为止的标准在线程上完全保持沉默)。 编译器通常使用一个简单的检查和增量的静态布尔实现 – 但不是在关键部分。 像下面的伪代码:

 static Singleton& instance() { static bool initialized = false; static char s[sizeof( Singleton)]; if (!initialized) { initialized = true; new( &s) Singleton(); // call placement new on s to construct it } return (*(reinterpret_cast<Singleton*>( &s))); } 

所以这里有一个简单的线程安全Singleton(对于Windows)。 它为Windows CRITICAL_SECTION对象使用一个简单的类包装器,以便在调用main()之前让编译器自动初始化CRITICAL_SECTION 。 理想情况下,将使用一个真正的RAII关键部分类来处理在关键部分被closures时可能发生的exception,但这超出了本答案的范围。

基本的操作是,当一个Singleton实例被请求的时候,会产生一个锁,如果需要的话就创build一个Singleton,然后释放锁并返回Singleton引用。

 #include <windows.h> class CritSection : public CRITICAL_SECTION { public: CritSection() { InitializeCriticalSection( this); } ~CritSection() { DeleteCriticalSection( this); } private: // disable copy and assignment of CritSection CritSection( CritSection const&); CritSection& operator=( CritSection const&); }; class Singleton { public: static Singleton& instance(); private: // don't allow public construct/destruct Singleton(); ~Singleton(); // disable copy & assignment Singleton( Singleton const&); Singleton& operator=( Singleton const&); static CritSection instance_lock; }; CritSection Singleton::instance_lock; // definition for Singleton's lock // it's initialized before main() is called Singleton::Singleton() { } Singleton& Singleton::instance() { // check to see if we need to create the Singleton EnterCriticalSection( &instance_lock); static Singleton s; LeaveCriticalSection( &instance_lock); return s; } 

男人 – 这是“让更好的全球化”的废话。

这个实现的主要缺点(如果我没有让一些错误通过)是:

  • 如果new Singleton()抛出,锁不会被释放。 这可以通过使用真正的RAIIlocking对象而不是我在这里的简单locking对象来解决。 这也可以帮助使事情可移植,如果你使用像Boost的东西提供一个平台独立的包装的锁。
  • 这在main()被调用之后请求Singleton实例时保证了线程的安全性 – 如果在那之前调用它(比如在静态对象的初始化中),事情可能不起作用,因为CRITICAL_SECTION可能没有被初始化。
  • 每次请求实例时都必须执行locking。 正如我所说的,这是一个简单的线程安全实现。 如果你需要一个更好的(或者想知道为什么像双重检查锁技术是有缺陷的),请参阅Groo的答案链接的文件 。

看下一个标准(6.7.4节),它探索了静态本地初始化是如何安全的。 所以一旦标准的这一部分被广泛实施,迈尔的Singleton将是首选的实现。

我不同意许多答案。 大多数编译器已经这样实现了静态初始化。 一个明显的例外是Microsoft Visual Studio。

正确的答案取决于你的编译器。 它可以决定使它线程安全; 这不是“自然”的线程安全。

以下实现是否线程安全?

在大多数平台上,这不是线程安全的。 (附加通常的免责声明,说明C ++标准不知道线程,因此,在法律上,它不会说是否是。)

如果没有,为什么?

其原因不是没有阻止多个线程同时执行s的构造函数。

如何使线程安全?

Scott Meyers和Andrei Alexandrescu 撰写的“C ++和双重检查locking的危险”是关于线程安全单例的一个很好的论述。

正如MSalters所说:这取决于你使用的C ++实现。 检查文档。 至于另一个问题:“如果不是,为什么?” – C ++标准还没有提到有关线程的任何内容。 但是即将到来的C ++版本知道线程,并明确指出静态本地的初始化是线程安全的。 如果两个线程调用这样的函数,一个线程将执行一个初始化,而另一个将阻塞并等待它完成。