不幸的是,Matt 的答案具有C/C++ 内存模型不支持的所谓的双重检查锁定。(Java 1.5 及更高版本(我认为是 .NET)内存模型支持它。)这意味着在进行pObj == NULL
检查和获取锁(互斥锁)之间,pObj
可能已经在另一个线程上分配了. 线程切换发生在操作系统想要的任何时候,而不是在程序的“行”之间(在大多数语言中,编译后没有意义)。
此外,正如 Matt 承认的那样,他使用 aint
作为锁而不是 OS 原语。不要那样做。正确的锁需要使用内存屏障指令、可能的缓存行刷新等;使用操作系统的原语进行锁定。这一点尤其重要,因为使用的原语可以在操作系统运行的各个 CPU 行之间发生变化;在 CPU Foo 上有效的东西可能在 CPU Foo2 上无效。大多数操作系统要么本机支持 POSIX 线程 (pthread),要么将它们作为 OS 线程包的包装器提供,因此通常最好使用它们来说明示例。
如果您的操作系统提供了适当的原语,并且您绝对需要它来提高性能,那么您可以使用原子比较和交换操作来初始化共享全局变量,而不是执行这种类型的锁定/初始化。本质上,您编写的内容将如下所示:
MySingleton *MySingleton::GetSingleton() {
if (pObj == NULL) {
// create a temporary instance of the singleton
MySingleton *temp = new MySingleton();
if (OSAtomicCompareAndSwapPtrBarrier(NULL, temp, &pObj) == false) {
// if the swap didn't take place, delete the temporary instance
delete temp;
}
}
return pObj;
}
这仅在创建多个单例实例是安全的情况下才有效(每个线程碰巧同时调用 GetSingleton() 一个),然后扔掉额外的东西。Mac OS X 上提供的OSAtomicCompareAndSwapPtrBarrier
功能——大多数操作系统都提供类似的原语——检查是否pObj
存在NULL
,并且只有在存在时才实际将其设置temp
为它。这使用硬件支持实际上只执行一次交换并判断它是否发生。
如果您的操作系统提供介于这两个极端之间的另一个工具,那么可以利用的另一个工具是pthread_once
. 这使您可以设置仅运行一次的功能-基本上是通过执行所有锁定/障碍/等。你的诡计 - 无论它被调用了多少次或它被调用了多少个线程。