背景:在函数中使用局部静态变量作为单例模式的实现的一个问题是,如果多个线程同时第一次调用该函数,则静态变量的初始化可以做两次。
我的问题是,如果你将静态变量的初始化包含在一个关键部分中,是否会阻止双重初始化的发生?例如:
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 :(得分:5)
C ++ 11消除了锁定的需要。如果已经初始化静态局部变量,则并发执行应该等待。
§6.7 [stmt.dcl] p4
如果控件在初始化变量时同时进入声明,则并发执行应等待初始化完成。
对于C ++ 03,我们有:
§6.7 [stmt.dcl] p4
在进行任何其他初始化之前,将执行具有静态存储持续时间(3.7.1)的所有本地对象的零初始化(8.5)。具有使用常量表达式初始化的静态存储持续时间的POD类型(3.9)的本地对象在其首次输入块之前被初始化。允许实现在与a相同的条件下以静态存储持续时间执行其他本地对象的早期初始化 允许实现在命名空间范围内静态初始化具有静态存储持续时间的对象(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
与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;
}
答案 1 :(得分: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;
}
答案 2 :(得分: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);
}
}
答案 3 :(得分:0)
我不确定您的代码是否正确,但通常是的,它可以解决问题。