关键部分和单例模式

Set*_*gie 2 c++ singleton multithreading critical-section c++03

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

我的问题是,如果你将静态变量的初始化包含在一个关键部分中,是否会阻止双重初始化的发生?例:

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;
}
Run Code Online (Sandbox Code Playgroud)

或者是初始化是否神奇地完成(不是在声明/初始化时),比如在构造函数开始之前初始化变量成员?

我的问题是关于pre-C++ 11,因为根据Xeo的回答,C++ 11自己解决了这个问题.

Xeo*_*Xeo 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;
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

谢谢@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;
}
Run Code Online (Sandbox Code Playgroud)