保证一个类仅有一个实例,并提供一个访问它的全局访问点。
----引自《设计模式 可复用面向对象软件的基础》
#include <iostream>
#include <mutex>
class Singleton
{
public:
static Singleton* GetInstance() {
std::lock_guard<std::mutex> lk(mutex_);
if (instance_ == nullptr) {
instance_ = new Singleton();
}
return instance_;
}
~Singleton() {
std::cout << "Singleton destruct" << std::endl;
}
private:
Singleton() {
std::cout << "Singleton construct" << std::endl;
}
static Singleton* instance_;
static std::mutex mutex_;
};
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;
int main(void)
{
std::cout << "begin" << std::endl;
auto p = Singleton::GetInstance();
std::cout << "end" << std::endl;
return 0;
}
call_once具备互斥量这种能力,而且效率上,比互斥量消耗的资源更少
#include <iostream>
#include <mutex> //std::call_once std::once_flag
class Singleton
{
public:
static Singleton* GetInstance() {
std::call_once(once_flag_, []() {
instance_ = new Singleton();
});
return instance_;
}
~Singleton() {
std::cout << "Singleton destruct" << std::endl;
}
private:
Singleton() {
std::cout << "Singleton construct" << std::endl;
}
static Singleton* instance_;
static std::once_flag once_flag_;
};
Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::once_flag_;
int main(void)
{
std::cout << "begin" << std::endl;
auto p = Singleton::GetInstance();
std::cout << "end" << std::endl;
return 0;
}
不管是互斥量版本,还是call_once版本,多线程环境下都会存在判断互斥量或once_flag的状态,本质上还是会造成性能开销,可以做一点优化,即双重检查锁(DCL)
版本, 使用mutex的DCL版本如下(有问题版本,后面解释和优化):
#include <iostream>
#include <mutex>
class Singleton
{
public:
static Singleton* GetInstance() {
Singleton *tmp = instance_;
/**********内存屏障***********/
if (tmp == nullptr) {
std::lock_guard<std::mutex> lk(mutex_);
tmp = instance_;
if (tmp == nullptr) {
tmp = new Singleton();
/**********内存屏障***********/
instance_ = tmp;
}
}
return instance_;
}
~Singleton() {
std::cout << "Singleton destruct" << std::endl;
}
private:
Singleton() {
std::cout << "Singleton construct" << std::endl;
}
static Singleton* instance_;
static std::mutex mutex_;
};
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;
int main(void)
{
std::cout << "begin" << std::endl;
auto p = Singleton::GetInstance();
std::cout << "end" << std::endl;
return 0;
}
双重检查锁只是将锁的粒度更细化
代码中的/*************内存屏障************/
是必不可少的,否则可能存在某一个或多个现场获取的实例instance_是不完整的,或者叫未初始化完成的。原因是new Singleton包含分配地址空间,在地址空间上执行Singleton的构造函数,如果出现指令重排序
,就会造成提前获取到Singleton实例,但Singleton构造函数还未真正执行。
到这里,也就是说明DCL是失效的,怎么解决呢?
The double-checked locking pattern (DCLP) is a bit of a notorious case study in lock-free programming. Up until 2004, there was no safe way to implement it in Java. Before C++11, there was no safe way to implement it in portable C++.
volatile
修饰即可解决该问题,是语言提供的同步机制,禁止编译器和处理器进行重排序。volatile
不适用于多线程环境的同步语境,没有上述的能力,仅仅具备标识该对象是特种内存(禁止编译器和处理器进行冗余加载,废弃存储的优化而已),比如IO映射内存。在C++11提供atomic的相关类型和操作可以解决DCL失效问题。
【代码1】
#include <iostream>
#include <mutex>
#include <atomic>
class Singleton
{
public:
static Singleton* GetInstance() {
Singleton *tmp = instance_.load(); // 默认std::memory_order_seq_cst
if (tmp == nullptr) {
std::lock_guard<std::mutex> lk(mutex_);
tmp = instance_.load();// 默认std::memory_order_seq_cst
if (tmp == nullptr) {
tmp = new Singleton();
instance_.store(tmp);// 默认std::memory_order_seq_cst
}
}
return tmp;
}
~Singleton() {
std::cout << "Singleton destruct" << std::endl;
}
private:
Singleton() {
std::cout << "Singleton construct" << std::endl;
}
static std::atomic<Singleton*> instance_;
static std::mutex mutex_;
};
std::atomic<Singleton*> Singleton::instance_ {nullptr};
std::mutex Singleton::mutex_;
int main(void)
{
std::cout << "begin" << std::endl;
auto p = Singleton::GetInstance();
std::cout << "end" << std::endl;
return 0;
}
【代码2】
#include <iostream>
#include <mutex>
#include <atomic>
class Singleton
{
public:
static Singleton* GetInstance() {
Singleton *tmp = instance_.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);
if (tmp == nullptr) {
std::lock_guard<std::mutex> lk(mutex_);
tmp = instance_.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton();
std::atomic_thread_fence(std::memory_order_release);
instance_.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
~Singleton() {
std::cout << "Singleton destruct" << std::endl;
}
private:
Singleton() {
std::cout << "Singleton construct" << std::endl;
}
static std::atomic<Singleton*> instance_;
static std::mutex mutex_;
};
std::atomic<Singleton*> Singleton::instance_ {nullptr};
std::mutex Singleton::mutex_;
int main(void)
{
std::cout << "begin" << std::endl;
auto p = Singleton::GetInstance();
std::cout << "end" << std::endl;
return 0;
}
关于dclp模型相关资料请参考Double-Checked Locking is Fixed In C++11
关于c++11内存模型,内存顺序,原子类型请参考《C++并法编程实践》第5章(C++内存模型和原子类型上的操作)
关于volatile和原子类型的适用场景请参考《Effective Modern C++》(第 40 项:使用 std::atomic 实现并发,使用 volatile 实现特殊内存)
到这里我们还需要考虑单例对象的资源释放问题:
使用mutex版本实现:
方案1代码:
#include <iostream>
#include <mutex>
#include <memory>
class Singleton
{
public:
static Singleton* GetInstance() {
std::lock_guard<std::mutex> lk(mutex_);
if (instance_ == nullptr) {
instance_ = new Singleton();
}
return instance_;
}
~Singleton() {
std::cout << "Singleton destruct" << std::endl;
}
private:
Singleton() {
std::cout << "Singleton construct" << std::endl;
}
class GC {
public:
GC() {
std::cout << "GC construct" << std::endl;
}
~GC() {
std::cout << "GC destruct" << std::endl;
if (instance_ != nullptr) {
delete instance_;
instance_ = nullptr;
std::cout << "instance_ is deleted" << std::endl;
}
}
};
static GC gc;
static Singleton* instance_;
static std::mutex mutex_;
};
Singleton::GC Singleton::gc;
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;
int main(void)
{
std::cout << "begin" << std::endl;
auto p = Singleton::GetInstance();
std::cout << "end" << std::endl;
return 0;
}
GC construct
begin
Singleton construct
end
GC destruct
Singleton destruct
instance_ is deleted
方案2代码:
#include <iostream>
#include <mutex>
#include <memory>
class Singleton
{
public:
static Singleton* GetInstance() {
std::lock_guard<std::mutex> lk(mutex_);
if (instance_ == nullptr) {
instance_ = std::unique_ptr<Singleton>(new Singleton());
}
return instance_.get();
}
~Singleton() {
std::cout << "Singleton destruct" << std::endl;
}
private:
Singleton() {
std::cout << "Singleton construct" << std::endl;
}
static std::unique_ptr<Singleton> instance_;
static std::mutex mutex_;
};
std::unique_ptr<Singleton> Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;
int main(void)
{
std::cout << "begin" << std::endl;
auto p = Singleton::GetInstance();
std::cout << "end" << std::endl;
return 0;
}
begin
Singleton construct
end
Singleton destruct