我如何在Windows中创build一个线程安全的单例模式?

我一直在阅读有关线程安全的单例模式:

http://en.wikipedia.org/wiki/Singleton_pattern#C.2B.2B_.28using_pthreads.29

它在底部说,唯一安全的方法是使用pthread_once – 这在Windows上不可用。

这是保证线程安全初始化的唯一方法吗?

我已经读过这个线程:

用C ++线程安全地构build一个单例

而且似乎暗示了一个primefaces操作系统级别的交换和比较函数,我在Windows上假设的是:

http://msdn.microsoft.com/en-us/library/ms683568.aspx

这可以做我想要的吗?

编辑:我想懒惰的初始化,并为此只有一个类的实例。

在另一个网站上有人提到在命名空间内使用全局(他将单例描述为反模式) – 它怎么可能是一个“反模式”?

一般承认的答案:
我已经接受Josh的答案,因为我正在使用Visual Studio 2008 – NB:对于未来的读者,如果你不使用这个编译器(或2005) – 不要使用接受的答案!

编辑: 代码工作正常,除了返回语句 – 我得到一个错误:错误C2440:'返回':不能从'易变的单身*'转换为'单身人士*'。 我应该修改返回值是易变的Singleton *?

编辑:显然const_cast <>将删除挥发性限定符。 再次感谢乔希。

如果您正在使用Visual C ++ 2005/2008,则可以使用双重检查的锁定模式,因为“ volatile变量表现为fence ”。 这是实现延迟初始化单例的最有效的方法。

来自MSDN杂志:

Singleton* GetSingleton() { volatile static Singleton* pSingleton = 0; if (pSingleton == NULL) { EnterCriticalSection(&cs); if (pSingleton == NULL) { try { pSingleton = new Singleton(); } catch (...) { // Something went wrong. } } LeaveCriticalSection(&cs); } return const_cast<Singleton*>(pSingleton); } 

每当你需要访问单例时,只需调用GetSingleton()。 第一次被调用时,静态指针将被初始化。 在初始化之后,NULL检查将防止只读指针的锁定。

不要在任何编译器上使用它,因为它不可移植。 该标准没有保证这将如何工作。 Visual C ++ 2005明确地增加了volatile的语义以使其成为可能。

您必须在代码的其他地方声明并初始化CRITICAL SECTION 。 但是初始化很便宜,所以懒惰初始化通常并不重要。

保证单例的跨平台线程安全初始化的一个简单方法是应用程序启动任何其他线程之前(在至少一个单线程中调用静态成员函数)任何其他线程将访问单身人士)。

通过互斥/关键部分的常用方式确保线程安全访问单例。

懒惰初始化也可以使用类似的机制来实现。 遇到的常见问题是,提供线程安全所需的互斥体通常在单例本身中初始化,这只是将线程安全问题推到了互斥/临界部分的初始化。 解决这个问题的一种方法是在应用程序的主线程中创建并初始化互斥/关键部分,然后通过调用静态成员函数将其传递给单例。 然后,使用这个预初始化的互斥/关键部分,可以以线程安全的方式发生单身人士的重量级初始化。 例如:

 // A critical section guard - create on the stack to provide // automatic locking/unlocking even in the face of uncaught exceptions class Guard { private: LPCRITICAL_SECTION CriticalSection; public: Guard(LPCRITICAL_SECTION CS) : CriticalSection(CS) { EnterCriticalSection(CriticalSection); } ~Guard() { LeaveCriticalSection(CriticalSection); } }; // A thread-safe singleton class Singleton { private: static Singleton* Instance; static CRITICAL_SECTION InitLock; CRITICIAL_SECTION InstanceLock; Singleton() { // Time consuming initialization here ... InitializeCriticalSection(&InstanceLock); } ~Singleton() { DeleteCriticalSection(&InstanceLock); } public: // Not thread-safe - to be called from the main application thread static void Create() { InitializeCriticalSection(&InitLock); Instance = NULL; } // Not thread-safe - to be called from the main application thread static void Destroy() { delete Instance; DeleteCriticalSection(&InitLock); } // Thread-safe lazy initializer static Singleton* GetInstance() { Guard(&InitLock); if (Instance == NULL) { Instance = new Singleton; } return Instance; } // Thread-safe operation void doThreadSafeOperation() { Guard(&InstanceLock); // Perform thread-safe operation } }; 

然而,完全避免使用单身人士是有充分理由的(为什么他们有时被称为反模式 ):

  • 他们本质上是荣耀的全球变数
  • 它们可能导致应用程序不同部分之间的高度耦合
  • 他们可以使单元测试变得更加复杂或不可能(因为用真实的实现来交换真实的单例很困难)

另一种方法是利用“逻辑单例”,在主线程中创建并初始化一个类的单个实例,并将其传递给需要它的对象。 如果有许多要创建为单例的对象,这种方法可能会变得很笨重。 在这种情况下,不同的对象可以被捆绑成一个单独的“上下文”对象,然后在必要时传递。

虽然我喜欢公认的解决方案,但我还是发现了另一个有希望的领导,并认为我应该在这里分享一下: 一次初始化(Windows)

您可以使用操作系统原语(如互斥锁或临界区)来确保线程安全初始化,但是每次访问单例指针(由于获取锁定)都会导致开销。 这也是不可移植的。

对于这个问题,你需要考虑一个问题。 你需要…

  1. 一个类的唯一一个实例是实际创建的
  2. 可以创建一个类的许多实例,但只能有一个真正的类的确定实例

网上有很多样本用C ++实现这些模式。 这是一个代码项目示例

下面解释了如何在C#中完成它,但是完全相同的概念适用于任何支持单例模式的编程语言

http://www.yoda.arachsys.com/csharp/singleton.html

你需要决定什么是你想懒惰初始化或不。 惰性初始化意味着包含在单例中的对象是在第一次调用它时创建的。例如:

 MySingleton::getInstance()->doWork(); 

如果这个调用不是直到以后才进行的话,线程之间的竞争状态就有危险,正如文章中所解释的那样。 但是,如果你把

 MySingleton::getInstance()->initSingleton(); 

在你的代码开始时,你认为它是线程安全的,那么你不再懒惰的初始化,当你的应用程序启动时,你将需要“一些”更多的处理能力。 但是,如果你这样做,它将解决很多有关竞赛条件的问题。

如果你正在寻找一个更便携,更简单的解决方案,你可以转向提升。

boost :: call_once可以用于线程安全初始化。

它非常简单易用,并将成为下一个C ++ 0x标准的一部分。

这个问题并不要求单身人士是懒惰构造或不。 由于许多答案假定,我假设对于第一句话讨论:

鉴于语言本身不是线程感知的事实,加上优化技术,编写一个可移植的可靠的c ++单例非常困难(如果不是不可能的话),请参阅Scott Meyers的“ C ++和双重检查锁定的危险 ”安德烈Alexandrescu。

我已经看到很多答案都是通过使用CriticalSection在Windows平台上同步对象,但是当所有线程都在单个处理器上运行时,CriticalSection只是线程安全的,今天可能并非如此。

MSDN引用:“单个进程的线程可以使用临界区对象进行互斥同步。”

http://msdn.microsoft.com/en-us/library/windows/desktop/ms682530(v=vs.85).aspx

进一步澄清:

临界区对象提供与由互斥对象提供的类似的同步,除了临界区只能由单个进程的线程使用。

现在,如果“懒构造”不是必需的,那么下面的解决方案既是跨模块安全的也是线程安全的,甚至是可移植的:

 struct X { }; X * get_X_Instance() { static X x; return &x; } extern int X_singleton_helper = (get_X_instance(), 1); 

这是跨模块安全的,因为我们使用本地范围的静态对象而不是文件/名称空间范围的全局对象。

这是线程安全的,因为:在输入main或DllMain之前,必须将X_singleton_helper赋值为正确的值。因为这个事实,它也不是懒惰构造的),在这个表达式中,逗号是一个运算符,而不是标点符号。

在这里显式使用“extern”来防止编译器优化它(关于Scott Meyers的文章,大敌是优化器),并且还使诸如pc-lint之类的静态分析工具保持沉默。 “在main / DllMain之前”是Scott Meyer在“Effective C ++ 3rd”第4项中称为“单线程启动部分”。

不过,我不确定是否允许编译器根据语言标准优化调用get_X_instance(),请注释。

有很多方法可以在Windows上进行线程安全的Singleton *初始化。 其实有些甚至是跨平台的。 在你链接到的SO线程中,他们正在寻找一个在C中被懒惰地构造的单例,这是一个更具体一点,考虑到你正在工作的内存模型的复杂性,可能有点棘手。

  • 你永远不应该使用它