Singleton实现时要注意的地方


对于lazy initialization的singleton,实现起来总的来说有三种方式:
1.double checked locking
2.Magic Static(N2660
3.std::call_once

第一种对coding能力要求较高,写的时候容易出错。而第二种和第三种则不是所有平台都支持。

Windows Linux
DCL 可以 可以
Magic static >=VC 2015 >=GCC 4.3
std::call_once >=VC 2015 Update 1 可以

Double checked locking

Intel CPU上不用考虑Memory order的问题,写操作不会被reorder。
DCL最好的范例是chrome里面的lazy_instance。那个过于复杂,下面这段代码选自别的开源库:

template <class TYPE, class LOCK> TYPE * 
Singleton<TYPE, LOCK>::instance (void) { 
    // First check 
    TYPE* tmp = instance_; 
#if defined (ALPHA_MP) 
// Insert the CPU-specific memory barrier instruction 
// to synchronize the cache lines on multi-processor. 
asm ("mb"); 
#endif /* ALPHA_MP */ 
    if (tmp == 0) { 
        // Ensure serialization (guard 
        // constructor acquires lock_). 
        Guard<LOCK> guard (lock_); 
        // Double check. 
        tmp = instance_; 
        if (tmp == 0) { 
                tmp = new TYPE; 
#if defined (ALPHA_MP) 
asm ("mb"); 
#endif /* ALPHA_MP */ 
                instance_ = tmp; 
        } 
    return tmp; 
    }
 

之所以要引入一个tmp变量,完全是为了插入memory barrier指令。memory barrier的用途在于防止某个线程看见instance_已经被赋值,但是TYPE的构造函数尚未执行。令我吃惊的是,在某些CPU中,加锁也不足以保证内存的写入顺序吗?
下面是JAVA中的标准写法
public static LazySingleton getInstance() {   
    if (m_instance == null) {   
       synchronized (LazySingleton.class) {   
            if (m_instance == null) {   
               m_instance = new LazySingleton();   
           }   
        }   
    }   
    return m_instance;   
}
并且,m_instance 必须加上volatile关键字。volatile它来保证顺序。

Magic Static

VC 2015以后function-local statics 才是 thread-safe for construction and destruction。问题是,如果你的代码是用更早版本的VC编译的,那么不会有任何警告。静静的等捉虫吧。

call_once

VC 2013及以前版本,call_once有比较严重的性能问题
VC 2015 RTM中call_once()有一个deadlock的bug: https://connect.microsoft.com/VisualStudio/feedback/details/811192 。
VS2015 Update 1中加入了Constexpr Static Initializers的支持。有了它之后,call_once才能被用在静态变量的构造函数中。否则,当你调用call_once的时候,如果call_once的flag还没被初始化,那么多糟糕啊!说来说去,这都是搞C++的自己给自己制造麻烦。如果是用C API,就没有这么多问题了。
Windows API中初始化INIT_ONCE的方式是:
static INIT_ONCE g_InitOnce = INIT_ONCE_STATIC_INIT; // Static initialization
其中INIT_ONCE_STATIC_INIT等于0。这里的g_InitOnce是一个全局变量。编译器可以保证只要程序的镜像一载进来,在执行任何code之前,这个变量就是已经被初始化好的。而C++就得依赖于constexpr关键字了。
VC下once_flag的定义:
// STRUCT once_flag
struct once_flag
{ // opaque data structure for call_once()
constexpr once_flag() _NOEXCEPT
 : _Opaque(0)
 { // default construct
 }

once_flag(const once_flag&) = delete;
once_flag& operator=(const once_flag&) = delete;

void *_Opaque;
};

并且有: sizeof(once_flag._Opaque) == sizeof(INIT_ONCE)



此博客中的热门博文

少写代码,多读别人写的代码

在windows下使用llvm+clang

tensorflow distributed runtime初窥