这个问题的通常解决方案是使用一个可以零初始化或常量初始化的静态对象与原子操作相结合,将自己“引导”到一个可以安全调用更复杂初始化的位置。
零和常量初始化保证在非常量初始化之前发生,并且由于它有效地同时发生,它不依赖于初始化顺序。
使用延迟初始化的对象
一个非常简单的示例将使用指向全局静态实例的零初始化指针,它指示静态是否已被初始化,如下所示:
class A
{
private:
volatile static A* Instance; // zero-initialized to NULL
public:
static A& GetInstance() {
A* inst = Instance;
if (!inst) {
A* inst = new Instance(...);
A* cur = InterlockedCompareExchange(&Instance, newInst, 0);
if (cur) {
delete inst;
return *cur;
}
}
return *inst;
}
};
上述方法的缺点是A
,如果两个(或更多)线程最初都A::Instance
视为空,则可能会创建两个(或更多)一个对象。代码正确地只选择了一个A
对象作为返回给所有调用者的真正的静态全局对象,而其他对象则被简单地删除,但是当一个进程中甚至不可能创建多个Instance
对象时,这可能是一个问题(例如,因为它由一些基本的单例资源支持,也许是一些硬件资源的句柄)。如果创建多个,也会有一些浪费的工作Instance
,如果创建过程很昂贵,这可能很重要。
这种模式有时被称为racy single-check。
使用延迟初始化的互斥体
避免上述陷阱的更好解决方案是使用互斥锁来保护单例的创建。当然,现在互斥锁初始化也有同样的排序问题,但是我们可以使用上面的技巧来解决这个问题(我们知道创建多个互斥对象是可以的)。
class MutexHolder
{
private:
volatile static CRITICAL_SECTION* cs; // zero-initialized to NULL
public:
static CRITICAL_SECTION* get() {
A* inst = cs;
if (!inst) {
CRITICAL_SECTION* inst = new CRITICAL_SECTION();
InitializeCriticalSection(inst);
CRITICAL_SECTION* cur = InterlockedCompareExchange(&cs, newInst, 0);
if (cur) {
DeleteCriticalSection(inst);
delete inst;
return *cur;
}
}
return *inst;
}
};
class A
{
private:
static MutexHolder mutex;
static A* Instance; // zero-initialized to NULL
public:
static A& GetInstance() {
A* inst;
CRITICAL_SECTION *cs = mutex.get();
EnterCriticalSection(cs);
if (!(inst = Instance)) {
inst = Instance = new A(...);
}
EnterCriticalSection(cs);
return inst;
}
};
这里MutexHolder
是一个围绕 WindowsCRITICAL_SECTION
对象的可重用包装器,它在方法内部执行惰性和线程安全的初始化get()
,并且可以进行零初始化。然后MutexHolder
将其用作经典互斥锁,以保护A
内部静态对象的创建A::GetInstance
。
您可以GetInstance
使用双重检查锁定以牺牲一些复杂性为代价来加快速度:与其CRITICAL_SECTION
无条件地获取,不如先检查是否Instance
已设置(如第一个示例),然后如果是则直接返回它。
初始化一次执行一次
最后,如果您的目标是 Windows Vista 或更高版本,Microsoft 添加了一个直接处理此问题的现成工具:InitOnceExecuteOnce。您可以在此处找到一个工作示例。这与 POSIX 大致类似pthead_once
并且有效,因为初始化是使用常量执行的INIT_ONCE_STATIC_INIT
。
在您的情况下,它看起来像:
INIT_ONCE g_InitOnce = INIT_ONCE_STATIC_INIT;
A* g_AInstance = 0;
BOOL CALLBACK MakeA(
PINIT_ONCE InitOnce,
PVOID Parameter,
PVOID *lpContext)
{
g_AInstance = new A(...);
return TRUE;
}
class A
{
private:
public:
static A& GetInstance() {
// Execute the initialization callback function
bStatus = InitOnceExecuteOnce(&g_InitOnce,
MakeA,
NULL,
NULL);
assert(bStatus);
return *g_AInstance;
}
};
Raymond Chen 写了一篇关于此功能的博客文章,这也有助于阅读。