2

背景:在函数中使用局部静态变量作为单例模式的实现的一个问题是,如果多个线程同时第一次调用该函数,则可以完成静态变量的初始化两次。

我的问题是,如果您将静态变量的初始化包装在关键部分中,这会阻止双重初始化的发生吗?例子:

CRITICAL_SECTION cs;

Class get_class_instance() {
    EnterCriticalSection(&cs);

    // is the initialisation of c done inside the critical section?
    static Class c = Class(data);

    LeaveCriticalSection(&cs);

    return c;
}

还是神奇地完成了初始化(而不是在声明/初始化时),就像在构造函数开始之前初始化变量成员一样?

我的问题专门针对 C++11 之前的版本,因为根据 Xeo 的回答,C++11 会自行解决这个问题。

4

4 回答 4

5

C++11 消除了对锁定的需求。如果静态局部变量已经被初始化,并发执行将等待。

§6.7 [stmt.dcl] p4

如果在初始化变量时控制同时进入声明,则并发执行将等待初始化完成。


对于 C++03,我们有这个:

§6.7 [stmt.dcl] p4

具有静态存储持续时间 (3.7.1) 的所有本地对象的零初始化 (8.5) 在任何其他初始化发生之前执行。POD 类型 (3.9) 的本地对象具有用常量表达式初始化的静态存储持续时间,在首次进入其块之前对其进行初始化。允许实现在与允许实现在命名空间范围内静态初始化具有静态存储持续时间的对象(3.6.2)相同的条件下执行具有静态存储持续时间的其他本地对象的早期初始化。否则,此类对象在控件第一次通过其声明时被初始化

最后一部分很重要,因为它适用于您的代码。控制第一次进入get_class_instance()时,先经过临界区的初始化,再经过单例的声明(这样会在临界区内初始化),再经过临界区的反初始化。

所以从理论上讲,你的代码应该是安全的。

现在,这可以改进,因为不进入每个函数调用的关键部分。@Chethan 的基本思想是合理的,所以我们将以此为基础。但是,我们也将避免动态分配。然而,为此,我们依赖于 Boost.Optional:

#include <boost/optional.hpp>

Class& get_class_instance() {
    static boost::optional<Class> c;
    static bool inited;

    if (!inited){
        EnterCriticalSection(&cs);

        if(!c)
            c = Class(data);

        LeaveCriticalSection(&cs);
        inited = true;
    }

    return *c;
}

Boost.Optional 避免了默认初始化,双重检查避免在每个函数调用时进入临界区。Class然而,这个版本在赋值中引入了对复制构造函数的调用。解决方案是就地工厂:

#include <boost/utility/in_place_factory.hpp>
#include <boost/optional.hpp>

Class& get_class_instance() {
    static boost::optional<Class> c;
    static bool inited;

    if (!inited){
        EnterCriticalSection(&cs);

        if(!c)
            c = boost::in_place(data);

        LeaveCriticalSection(&cs);
        inited = true;
    }

    return *c;
}

我要感谢@R。Martinho Fernandes 和@Ben Voigt 合作完成了这个最终解决方案。如果您对该过程感兴趣,请随时查看成绩单


现在,如果您的编译器已经支持某些 C++11 功能,但不支持静态初始化的东西,您还可以std::unique_ptr结合使用 Placement new 和静态对齐缓冲区:

#include <memory> // std::unique_ptr
#include <type_traits> // alignment stuff

template<class T>
struct destructor{
    void operator(T* p) const{
    if(p) // don't destruct a null pointer
        p->~T();
    }
};

Class& get_class_instance() {
    typedef std::aligned_storage<sizeof(Class),
        std::alignment_of<Class>::value>::type storage_type;
    static storage_type buf;
    static std::unique_ptr<Class, destructor> p;
    static bool inited;
    
    if (!inited){
        EnterCriticalSection(&cs);
    
        if(!p)
            p.reset(new (&buf[0]) Class(data));
    
        LeaveCriticalSection(&cs);
        inited = true;
    }
    
    return *p;
}
于 2012-01-02T03:41:46.103 回答
2

用关键部分包装初始化肯定会有所帮助!我会使用下面的代码来确保我们的静态变量只初始化一次。

CRITICAL_SECTION cs;

Class& get_class_instance() {
    static Class *c; //by default, global and static variables are assigned default values. Hence c will be NULL when the program starts.

    EnterCriticalSection(&cs);

    if(c == NULL)
        c = new Class(data);

    LeaveCriticalSection(&cs);

    return *c;
}
于 2012-01-02T03:47:03.307 回答
2

对于 C++03,您需要无锁地更新状态。这是因为你不能在没有初始化的情况下使用锁,这将再次递归地遇到完全相同的问题,因为你需要线程安全地初始化你将用于线程安全初始化的锁。哎呀。对于全局变量和一些无锁指令,您只能依赖零初始化。这也快得多。

您可以使用状态变量和无锁 CAS 指令来解决此问题。

enum State {
    ObjectUninitialized,
    ObjectInitializing,
    ObjectInitialized
};

volatile std::size_t state; // Must be initialized to 0- done by compiler
                            // before main(). All globals are 0
                            // Also must be word sized

T* func() {
    static char buffer[sizeof(T)];
    long result = InterlockedCompareExchange(&state, ObjectInitializing, ObjectUninitialized);
    if (result == ObjectInitialized) {
        return reinterpret_cast<T*>(buffer);
    }
    if (result == ObjectInitializing) {
        while (state == ObjectInitializing); // read is atomic for word-size variables
        return reinterpret_cast<T*>(buffer); // and use volatile to force compiler to add     
    }
    if (result == ObjectUninitialized) {
        new (buffer) T();
        InterlockedExchange(&state, ObjectInitialized);
        return reinterpret_cast<T*>(buffer);
    }
}
于 2012-01-02T21:11:48.340 回答
0

我不确定您的代码具体是否正确,但通常是的,它会解决问题。

于 2012-01-02T03:41:59.420 回答