C++的多种单例模型实现
单例概念
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例的懒汉实现
利用C++11的static线程安全性
class Instance {
public:
static Instance& getInstance() {
static Instance instance;
return instance;
}
private:
Instance() = default;
};
利用call_once函数保证的只执行一次的语义;
class Instance {
public:
static Instance& getInstance() {
static std::once_flag flag;
std::call_once(flag, []() {instance.reset(new Instance()); });
return *instance;
}
private:
Instance() = default;
static std::shared_ptr<Instance> instance;
};
std::shared_ptr<Instance> Instance::instance;
单例的饿汉实现
class Instance {
public:
static Instance& getInstance() {
return instance;
}
private:
Instance() = default;
static Instance instance;
};
Instance Instance::instance;
或者
class Instance {
public:
static Instance& getInstance() {
return *instance;
}
private:
Instance() = default;
static Instance* instance;
};
Instance* Instance::instance=new Instance();
有问题的双重检查锁
问题源码
//双重检测锁,存在问题
class Instance {
public:
static Instance& getInstance() {
if (instance == nullptr) {
std::unique_lock<std::mutex>lock(mx);
if (instance == nullptr)
instance = new Instance();
}
return *instance;
}
private:
Instance() = default;
static std::mutex mx;
static Instance* instance;
};
Instance* Instance::instance;
以上代码的问题原因
instance = new Instance()执行有三个过程:
A. 分配内存;
B. 初始化构建对象;
C. 返回内存指针给instance变量;
其中根据依赖关系,A必然在B和C之前,其它的顺序是无法保证的;
看一下这种情况:
线程1:获取lock锁后,执行A->C,然后释放锁;
线程2:首先看到instance不为nullptr,直接返回instance,而这时候,instance还没有还得及被线程1初始化;
注:因为线程2没有加锁,不会有内存屏障。那么很自然地,加上内存屏障就可以避免这种情况了。
基于atomic的双重检查锁
源码
//双重检测锁
class Instance {
public:
//或者这里的load和store采用默认的语义memory_order_seq_cst
static Instance& getInstance() {
if (instance.load(std::memory_order_acquire) == nullptr) {
std::unique_lock<std::mutex>lock(mx);
if (instance.load(std::memory_order_relaxed) == nullptr)
instance.store(new Instance(), std::memory_order_release);
}
return *instance;
}
private:
Instance() = default;
static std::mutex mx;
static std::atomic<Instance*> instance;
};
std::atomic<Instance*> Instance::instance;
说明
C++11提供的atomic,可以实现内存屏障的作用。保证了线程1必须在store之前,将instance构建好(构建的3个过程是写操作,不会重排到store之后),这是由store的release的语义保证的。而线程2使用load的acquire语义保证了得到的instance必然是已经构建好的。
注:可以使用load/store的默认语义memory_order_seq_cst;
问:如果instance.load(std::memory_order_acquire)采用memory_order_relaxed语义,会有什么问题?
参考
https://www.cnblogs.com/muzzik/p/12298487.html
https://blog.csdn.net/iteye_10289/article/details/82551552