c++ - VC++6线程安全静态初始化

标签 c++ multithreading static-initialization

我首先要说的是,我已经知道在 C++11 标准中,静态局部初始化现在是线程安全的。但是,我仍然需要保持与 Microsoft Visual C++ 6 的兼容性,因此 C++11 行为不适用。

我有一个使用少量静态变量的静态库。我遇到了静态变量在初始化之前被使用的问题(单线程):

class A
{
private:
    static A Instance;
public:
    static A& GetInstance() { return Instance; }
};

// And then from a different file:

A.GetInstance();

A.GetInstance() 会返回一个未初始化的实例。所以我听从了这个建议 http://www.cs.technion.ac.il/users/yechiel/c++-faq/static-init-order-on-first-use-members.html并将我所有的静态变量移动到本地方法中。

class A
{
public:
    static A& GetInstance()
    {
        static A Instance;
        return Instance;
    }
};

我以为这解决了问题,但现在我发现东西并不总是正确初始化,因为我在启动期间创建了其他线程。

Raymond Chen 在 2004 年描述了这个问题:https://blogs.msdn.microsoft.com/oldnewthing/20040308-00/?p=40363 ,但似乎没有人有任何解决方案。任何人提到的唯一解决方案是使用互斥锁来防止从多个线程进行初始化。但这似乎是先有鸡还是先有蛋的问题。我所知道的每种类型的互斥锁都需要某种初始化才能使用。我怎样才能确保它在我第一次使用它之前被初始化。我想我必须将其设为本地静态。但我如何确保它从一个线程初始化?

如果我可以确保在初始化任何其他内容之前将一个内存位置初始化为已知值,我可以对其使用互锁操作来旋转等待以引导我的整个初始化。有没有办法确保一个内存位置在任何其他初始化发生之前处于多个线程的已知状态?或者任何一种可以在没有先有鸡还是先有蛋的问题的情况下完成的同步?

最佳答案

这个问题的通常解决方案是使用一个可以零初始化或常量初始化的静态对象,结合原子操作来“引导”自己进入一个可以安全地调用更复杂的初始化的位置。

零和常量初始化保证在非常量初始化之前发生,并且由于它实际上同时发生,因此不依赖于初始化顺序。

使用延迟初始化的对象

一个非常简单的例子是使用一个指向全局静态实例的零初始化指针,它指示静态是否已被初始化,如下所示:

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::Instance,则可能会创建两个(或更多)一个 A 对象> 为空。代码正确地只选择了一个 A 对象作为返回给所有调用者的真正静态全局对象,而其他对象则被简单地静默删除,但这可能是一个问题,因为它甚至不可能创建超过进程中的一个 Instance 对象(例如,因为它由一些基本的单例资源支持,可能是一些硬件资源的句柄)。如果创建多个 Instance,也会产生一些浪费的工作,如果创建过程代价高昂,这可能很重要。

这种模式有时被称为racy single-check

使用延迟初始化的互斥体

避免上述陷阱的更好解决方案是使用互斥锁来保护单例的创建。当然,现在 mutex 初始化也有同样的顺序问题,但我们可以使用上面的技巧来解决这个问题(我们知道创建多个 mutex 对象是可以的)。

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 是 Windows CRITICAL_SECTION 对象的可重用包装器,它在 get() 中执行延迟和线程安全的初始化> 方法,并且可以被零初始化。然后,此 MutexHolder 用作经典互斥锁,以保护 A::GetInstance 中静态 A 对象的创建。

您可以通过使用 double-checked locking 来提高 GetInstance 的速度,但代价是有些复杂。 : 而不是无条件地获取 CRITICAL_SECTION,首先检查 Instance 是否已设置(如第一个示例),如果已设置则直接返回。

初始化一次执行一次

最后,如果您的目标是 Windows Vista 或更高版本,Microsoft 添加了一个现成的工具来直接处理此问题:InitOnceExecuteOnce .你可以找到一个 worked example here .这与 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 写了一个blog entry about this function这也有助于阅读。

关于c++ - VC++6线程安全静态初始化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50262460/

相关文章:

c++ - 将 ‘double*’ 转换为 ‘boost::any*’

c - 线程堆栈分配

c++ - 如何使用cpprestsdk解析来自websocket_client的json数据

c++ - 即使元素不存在,C++ STL 中的 lower_bound 也会返回一个迭代器。如何避免这种情况?

java - 在 Java 中启用英特尔超线程

c++ - 在 C++ 中是否可以从在辅助线程中运行的函数在主线程中执行函数?

c - C中整数指针数组的静态初始化错误

c++ - MSVC 2017 违反单个翻译单元内的静态初始化顺序

java - 私有(private)静态属性变量导致空指针异常

c++ - 为什么在重定向 stdout 和 stdin 时 Python 的行为不符合预期?