曾经我发现我的单例模式(代码1)的实现严格来说不适合在多线程的环境中使用(至少对传统编译器来说是这样的)。后来改用了加锁的实现(代码2)。在一些对性能有要求的项目中我总是不想使用这个每次获取实例都加一次锁的方案。虽然当时也有代码3作为一个选项,但那时我对单例模式与C++的兼容感到失望(比如代码3必须在main函数执行前创建单例对象,即不支持延迟初始化)。后来事情发生了变化,在各个VC++版本中都找到了较满意的实现。尤其是在visual studio 2015以后,不仅满足延迟初始化而且支持嵌套调用(下文下划线部分说明的特性)
避免每次获取实例都要加一个互斥锁,基于不同的编译器可以有不同的实现,因为每个编译器对C++11的符合程度不同。
如果使用的是visual studio 2010及以下的开发环境(基本没有实现C++11的新特性),我可能放弃使用单例类转而使用类的全局对象,或者使用一个非延迟初始化(恶汉模式)的实现(代码3)。boost中单例的实现也是恶汉模式。和代码3相比增加了嵌套调用的支持:在main函数执行之前执行单例A的方法,并在其中调用单例B的方法。不过我感到boost的实现方式很晦涩。
在visual studio 2012/2013中我会考虑采用双检锁的方式实现(代码4)。因为visual studio 2012 实现了C++11的atomic功能。从而可以简单(不必编写很底层的操作——甚至采用汇编)、正确的实现双检锁模式。ins_读取和赋值是原子操作。ins_在读取和赋值时编译器会添加内存栅栏,避免编译器和处理器擅自变更代码的执行顺序。
这个双检锁的实现有些复杂,但它实现了延迟初始化并且避免每次获取实例时都加锁。我一定要在这里介绍这个模式也是因为网上有太多错误的双检锁的实现,它们没有实现线程安全。
在visual studio 2015中,已经实现了接近100%的C++11特性。在这个版本实现了“静态局部对象初始化的线程安全”。用了这个版本以上的开发环境后,就可以在多线程场景下使用代码1的实现,线程安全由编译器保证。
关于“静态局部对象初始化的线程安全”的文档如下:(1)在visual studio 2015 的更新说明文档中,Thread-Safe "Magic" Statics Static local variables are now initialized in a thread-safe way, eliminating the need for manual synchronization. Only initialization is thread-safe, use of static local variables by multiple threads must still be manually synchronized. The thread-safe statics feature can be disabled by using the /Zc:threadSafeInit- flag to avoid taking a dependency on the CRT. (C++11)。(2)在“ISOIEC 14882 2011”的第6.7.4节的描述为“If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.”
微软声称自己实现了这部分代码的安全,可以眼见为实,看看编译器背后做了什么(代码5)。
网上还有若干使用call_once实现单例的示例,其中有的是有问题的(代码6)。这个静态变量oc不是线程安全的。至少在visual studio 2015以前的版本编译是这样。如果是使用visual studio 2015反而不需要call_once了。如果once_flag的oc变量写成类作用域的静态成员,是能避免这个线程竞争问题的。
用C#实现单例如代码7所示。简单的不要不要的,安全的不要不要的。借助类库的类Lazy,C#可以直接实现。默认情况下Lazy内部的实现为双检锁模式。可以通过传参指定其他的模式,不过最优的选择还是默认的。
代码1:
class Singleton
{
public:
static Singleton* Get()
{
static Singleton s;
return &s;
}
private:
Singleton()
{
printf("hello");
}
};
代码2
class Singleton
{
public:
static Singleton* Get()
{
lock_guard<mutex> lock(mut_);
if (ins_ == nullptr)
{
ins_ = new Singleton();
}
return ins_;
}
static void Destroy()
{
lock_guard<mutex> lock(mut_);
if (ins_ != nullptr)
{
delete ins_;
ins_ = nullptr;
}
}
private:
Singleton()
{
printf("hello\n");
};
private:
static atomic<Singleton*> ins_;
static mutex mut_;
};
atomic<Singleton*> Singleton::ins_;
mutex Singleton::mut_;
代码3
class Singleton
{
public:
static Singleton* Get()
{
return &s;
}
private:
Singleton()
{
printf("hello");
}
private:
static Singleton s;
};
Singleton Singleton::s;
代码4
class Singleton
{
public:
static Singleton* Get()
{
Singleton* temp = ins_;
if (temp == nullptr)
{
lock_guard<mutex> lock(mut_);
temp = ins_;
if (temp == nullptr)
{
temp = new Singleton();
ins_ = temp;
}
}
return temp;
}
static void Destroy()
{
lock_guard<mutex> lock(mut_);
if (ins_)
{
delete ins_;
ins_ = nullptr;
}
}
private:
Singleton()
{
printf("hello\n");
};
private:
static atomic<Singleton*> ins_ = nullptr;
static mutex mut_;
};
atomic<Singleton*> Singleton::ins_;
mutex Singleton::mut_;
代码(图片)5
代码6
class Singleton
{
public://错误实现示例
static Singleton* Get()
{
//这个静态变量不是线程安全的。至少在visual studio 2015以前的版本编译是这样。
//如果是使用visual studio 2015反而不需要call_once了。
//如果once_flag的oc变量写成类的静态成员,是能避免这个线程竞争问题的。
static std::once_flag oc;
std::call_once(oc,[](){ ins_ = new Singleton(); });
return ins_;
}
private:
Singleton()
{
printf("hello\n");
};
private:
static Singleton* ins_;
};
Singleton* Singleton::ins_;
代码7
class Singleton
{
public static Singleton Instance
{
get
{
return _lazy_ins.Value;
}
}
private Singleton()
{
Console.WriteLine("hello");
}
private static readonly Lazy<Singleton> _lazy_ins = new Lazy<Singleton>(() => { return new Singleton(); });
}