谈谈 单例模式

6 篇文章 0 订阅
本文详细介绍了C++中实现线程安全单例模式的多种方法,从最初的简单实现到考虑线程安全和内存泄漏问题,再到使用互斥锁和原子操作避免竞态条件。最后提到了C++11的静态成员初始化和std::call_once的使用,以简化代码并确保线程安全。文章深入讨论了内存序和并发问题,为理解和实践C++单例模式提供了宝贵的知识。
摘要由CSDN通过智能技术生成

单例模式

单例模式是最简单的一种设计模式,保证⼀个类仅有⼀个实例,并提供⼀个该实例的全局访问点。

class Singleton{
public:
static Singleton * GetInstance{
	if(instance == NULL)
		instance = new Singleton();
	return instance;
}
private:
Singleton(){}  //构造
Singleton(const Singleton&){} //拷贝构造
Singleton &operator = (const Singleton&){}
static Singleton *instance;
}
Singleton *Singleton::instance =  NULL;

这种的单例会有什么问题呢?会有两个问题,第一个是有线程安全问题,第二个new的对象没有delete,导致内存泄漏。那我们要怎么改呢?加锁!

class Singleton{
public:
static Singleton * GetInstance{
	if(instance == NULL){
		instance = new Singleton();
		atexit(Destructor);
	}
	return instance;
}
private:
static void Destructor(){
	if(NULL != instance)
	delete instance;
	instance = NULL;
}
Singleton(){}  //构造
Singleton(const Singleton&){} //拷贝构造
Singleton &operator = (const Singleton&){}
static Singleton *instance;
}
Singleton *Singleton::instance =  NULL;

添加了一个析构函数atexit(),函数参数是一个函数指针,函数指针指向一个没有参数也没有返回值的函数。
#include
*intatexit(void(func)(void));
atexit()成功时返回零,失败时返回非零。
这样在函数退出时,就可以自动调用析构函数。
但是还是线程不安全。
3.

class Singleton{
public:
static Singleton * GetInstance{
//std::lock_guard<std::mutex> lock(_mutex); // 3.1 切换线程
if(instance == NULL){
std::lock_guard<std::mutex> lock(_mutex); // 3.2
	if(instance == NULL){
		instance = new Singleton();
		atexit(Destructor);
		}
	}
	return instance;
}
private:
static void Destructor(){
	if(NULL != instance)
	delete instance;
	instance = NULL;
}
Singleton(){}  //构造
Singleton(const Singleton&){} //拷贝构造
Singleton &operator = (const Singleton&){}
static Singleton *instance;
static std::mutex mutex;
}
Singleton *Singleton::instance =  NULL;
std::mutex Singleton::mutex;   //互斥锁初始化

这里需要double-check 来预防多线程带来的问题。但是使用3.1的这样的加锁,不管instance 是不是null, 都会导致产生system call的问题,资源大大浪费,所以衍生出3.2这样的加锁方式。但是这样也会带来一个问题,因为c++内存的reorder问题(推荐 这篇blog c++11的内存序)。
因为我们知道一个class new一个新对象的时候,会生成 1.分配内存;2.调用构造器;3.赋值操作。reorder操作就是不按这样的顺序进行初始化,如果有个线程先进行赋值操作,然后再调用构造器操作。会导致第二个线程查询instance时,不为空,导致直接return。

class Singleton{
public:
static Singleton * GetInstance{
Singleton* tmp = _instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);//获取内存屏障
if(instance == NULL){
std::lock_guard<std::mutex> lock(_mutex); 
tmp = instance.load(std::memory_order_relaxed);
	if(instance == NULL){
		tmp = new Singleton;
		std::atomic_thread_fence(std::memory_order_release);//释放内存屏障
		instance.store(tmp, std::memory_order_relaxed);
		atexit(Destructor);
		}
	}
	return tmp;
}
private:
static void Destructor(){
	if(NULL != instance)
	delete instance;
	instance = NULL;
}
Singleton(){}  //构造
Singleton(const Singleton&){} //拷贝构造
Singleton &operator = (const Singleton&){}
static std::atomic<Singleton*> instance;
static std::mutex mutex;
}
std::atomic<Singleton*> Singleton::instance;
std::mutex Singleton::mutex;   //互斥锁初始化

对_instance进行原子操作,这样就保证了线程安全。但是这样是不是未免太麻烦了。c++ effective中提供了一种简单的方案:

class Singleton{
public:
static Singleton &  GetInstance{
	static Singleton  instance;
	return instance;
}
private:
Singleton(){}  //构造
Singleton(const Singleton&){} //拷贝构造
Singleton &operator = (const Singleton&){}
static Singleton *instance;
}
Singleton *Singleton::instance =  NULL;

这是c++11的 magic static 特性:如果当变量在初始化的时候,并发同时进⼊声明语句,并发
线程将会阻塞等待初始化结束。

class SingleTon {
 public:
  static T& instance() {
    std::call_once(once_, &SingleTon::init);
    return *value_;
  }

 private:
  SingleTon();
  ~SingleTon();

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

  static void init() { value_ = new T(); }
  static T* value_;

  static std::once_flag once_;
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值