2012-01-02 29 views
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; 
} 

或者被初始化(在声明/初始化点不)神奇完成,就像变量部件的构造开始前初始化?

我的问题是关于pre-C++ 11的具体问题,因为根据Xeo的回答,C++ 11自己处理这个问题。

+0

通过编辑,我应该删除我的答案,因为它不再相关。 :P – Xeo 2012-01-02 03:49:36

+0

@Xeo对不起,我应该更具体一些,但是我正在处理一个不支持C++ 11的功能的编译器,尽管我赞成你的回答,谢谢 – 2012-01-02 03:50:31

回答

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合作完成这一最终解决方案。如果您对此过程感兴趣,请随时查看transcript。现在


,如果你的编译器支持一些C++ 11的功能了,但不是静态初始化的东西,你也可以使用std::unique_ptr与布局新和静态对齐的缓冲,结合:

#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; 
} 
+0

对静态类也是如此会员? – littleadv 2012-01-02 03:43:39

+0

啊,C++ 11来拯救。那么pre-C++ 11呢? – 2012-01-02 03:44:50

+0

@Seth:Pre-C++ 11甚至不知道并发执行。 :P我不太了解并发性,然后对你的想法发表评论。 ( – Xeo 2012-01-02 03:46:45

0

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

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; 
} 
+0

是的,这段代码更有意义 – littleadv 2012-01-02 03:54:35

+0

即使你保存了一个指向堆栈变量的指针 – 2012-01-02 03:55:52

+0

哦!是的。我的错。让我们把它分配在堆上! – 2012-01-02 03:57:25

2

对于C++ 03,你需要locklessly更新的状态。这是因为,如果不初始化它,就不能使用锁,因为您需要线程安全地初始化要用于线程安全初始化的锁,所以会再次递归地运行完全相同的问题。哎呦。您只能依靠零初始化来设置全局变量和一些无锁指令。这也快得多。

您可以使用状态变量和无锁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); 
    } 
}