我一直在阅读有关线程安全的单例模式:
http://en.wikipedia.org/wiki/Singleton_pattern#C.2B.2B_.28using_pthreads.29
它在底部说,唯一安全的方法是使用pthread_once - 这在Windows上不可用.
这是保证线程安全初始化的唯一方法吗?
我在SO上读过这个帖子:
C++中单例的线程安全延迟构造
并且似乎暗示了原子操作系统级别的交换和比较功能,我在Windows上假设:
http://msdn.microsoft.com/en-us/library/ms683568.aspx
这可以做我想要的吗?
编辑:我想懒惰的初始化,因为只有一个类的实例.
另一个网站上有人提到在命名空间中使用全局(并且他将单例描述为反模式) - 它如何成为"反模式"?
一般承认的答案:
我已经接受了Josh的回答,因为我正在使用Visual Studio 2008 - 注意:对于未来的读者,如果您不使用此编译器(或2005) - 请勿使用接受的答案!
编辑: 除了return语句之外代码工作正常 - 我收到错误:错误C2440:'return':无法从'volatile Singleton*'转换为'Singleton*'.我应该将返回值修改为易失性Singleton*吗?
编辑:显然const_cast <>将删除volatile限定符.再次感谢Josh.
保证单例的跨平台线程安全初始化的一种简单方法是在应用程序启动任何其他线程之前(或至少在应用程序的主线程中显式执行它(通过调用单例上的静态成员函数))任何其他将访问单例的线程).
然后通过互斥锁/关键部分以通常的方式确保线程安全访问单例.
也可以使用类似的机制来实现延迟初始化.遇到的常见问题是提供线程安全所需的互斥体通常在单例本身中初始化,这只会将线程安全问题推到互斥锁/临界区的初始化.解决此问题的一种方法是在应用程序的主线程中创建并初始化互斥锁/临界区,然后通过调用静态成员函数将其传递给单例.然后,使用这个预先初始化的互斥/临界区,以线程安全的方式进行单例的重量级初始化.例如:
// 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 } };
但是,有充分的理由避免完全使用单例(以及为什么它们有时被称为反模式):
它们本质上是美化的全球变量
它们可以导致应用程序的不同部分之间的高度耦合
它们可以使单元测试更复杂或不可能(由于使用虚假实现交换真正的单例很困难)
另一种方法是使用"逻辑单例",您可以在主线程中创建和初始化类的单个实例,并将其传递给需要它的对象.如果有许多对象要创建为单例,则此方法可能会变得难以处理.在这种情况下,不同的对象可以捆绑到一个"Context"对象中,然后在必要时传递.
如果您正在使用Visual C++ 2005/2008,则可以使用双重检查锁定模式,因为" volatile变量表现为fences ".这是实现延迟初始化单例的最有效方法.
来自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(pSingleton); }
每当您需要访问单例时,只需调用GetSingleton()即可.第一次调用时,静态指针将被初始化.在初始化之后,NULL检查将阻止仅仅读取指针的锁定.
不要在任何编译器上使用它,因为它不可移植.该标准不保证这将如何工作.Visual C++ 2005明确地添加了volatile的语义以使其成为可能.
您必须在代码中的其他位置声明并初始化CRITICAL SECTION.但是初始化很便宜,所以延迟初始化通常并不重要.