创建型模式Creational Patterns之单例模式singleton

@著作权归作者所有:来自CSDN博客作者大胡子的艾娃的原创作品,如需转载,请注明出处https://blog.csdn.net/qq_43148810,否则将追究法律责任。
如有错误的地方欢迎指正,谢谢!

一、单例应用场景

1、应用程序的日志
2、应用的配置
3、多线程的线程池
4、内存池
等等
联想:和单例相类似的有“创建后不能拷贝,只能move”,标准库中有thread、mutex、lock_guard等

二、非常经典的的两个版本

1、懒汉版本(static对象)

class Singleton
{
private:
	Singleton() = default;
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
	~Singleton() = default;
public:
	static Singleton& getInstance()
	{
		static Singleton Inst;
		return Inst;
	}
};

2、饿汉版本(new pointer)

class Singleton
{
private:
	Singleton() = default;
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
	~Singleton() = default;
private:
	static Singleton* m_instance;
public:
	static Singleton* getInstance()
	{
	return m_instance;
	}
};
Singleton* Singleton::m_instance = new Singleton;

三、饿汉版本(new pointer)的多线程探讨

1、多线程非安全版本

class Singleton
{
private:
	Singleton() = default;
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
	~Singleton() = default;
public:
	static Singleton* getInstance();
private:
	static Singleton* m_instance;
};
Singleton* Singleton::m_instance = nullptr;

Singleton* Singleton::getInstance()
{
	if (m_instance == nullptr)
	{
		m_instance = new Singleton();
	}
	return m_instance;
}

非安全原因:多线程都同时第一次进入getInstance()函数对m_instance 进行空判,但都未创建对象,那么都会去new Singleton。

2、多线程安全版本,但锁的代价过高,不适合高并发和高频率获取

class Singleton
{
private:
	Singleton() = default;
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
	~Singleton() = default;

public:
	static Singleton* getInstance();

private:
	static std::mutex m_mutexSingleton;
	static Singleton* m_instance;
};

Singleton* Singleton::m_instance = nullptr;
std::mutex Singleton::m_mutexSingleton;

Singleton* Singleton::getInstance() 
{
	//std::lock_guard<std::mutex> guard(m_mutexSingleton);
	m_mutexSingleton.lock();
	if (m_instance == nullptr) 
	{
		m_instance = new Singleton();
	}
	m_mutexSingleton.unlock();
	return m_instance;
}

为什么代价过高:多个线程,只要有一个线程走完锁的部分,就会new Singleton,完成“写”操作,那么后面所有线程都只有“读”操作,就不会存在线程安全问题。单例本身就只创建一个,而多次被读取,读的过程常锁带来性性能损失过大。

3、存在内存读写reorder不安全的双检查锁
除getInstance()函数和上一示例(2)不一样,其他部分都相同。

Singleton* Singleton::getInstance() 
{
	if (m_instance == nullptr)
	{
		//std::lock_guard<std::mutex> guard(m_mutexSingleton);
		m_mutexSingleton.lock();
		if (m_instance == nullptr)
		{
			m_instance = new Singleton();
		}
		m_mutexSingleton.unlock();
	}
	return m_instance;
}

表面上看解决了上一版本中加锁导致的性能问题,看起来很完美,甚至被写入教科书,但是确实存在问题。reorder会导致双检查锁失效。“m_instance = new Singleton();”这段代码由于可以被某些编译器未知因素(操作系统、硬件、其他线程等)被编译器优化。实现可能有两种情况:1、先分配内存,再调用构造去初始化这块内存,最后将m_instance指针指向这块内存;2、先分配内存,再将m_instance指针指向这这段内存,最后调用构造去初始化这块内存。reorder就是是指1和2两种不定的情况。如果是情况1是双检查锁是没有问题;但是出现情况2就会出现第一个进程走到只对m_instance分配内存并将指针指向该内存,但还未构造初始化该内存,后面的线程此时如果走到第一个检查的空判,就会返回指向未经过构造的m_instance指针。此时reorder就会导致双检查锁失效。
【备注】有的编程语言不会出现情况2,也有的非标C++有关键字(volatile)声明来处理reorder,const和指针都可以是volatile。

4、利用std::once_flag类和std::call_once函数保证创建单例只执行一次,而每次获取单例对:call_once判断。
极力推荐使用该方法

class Singleton
{
private:
	Singleton() = default;
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
	~Singleton() = default;

	class CSingleton
	{
	public:
		~CSingleton()
		{
			if (Singleton::m_instance)
			{
				delete Singleton::m_instance;
				Singleton::m_instance = nullptr;
			}
		}
	};

	static void CreateInstance()
	{
		std::cout << "首次执行CreateInstance!\n";
		static CSingleton CS;
		m_instance = new Singleton();
	}
private:
	static Singleton* m_instance;
	static std::once_flag m_flag;
public:
	static Singleton* getInstance()
	{
		std::call_once(m_flag, CreateInstance);
		return m_instance;
	}
};
Singleton* Singleton::m_instance = nullptr;
std::once_flag Singleton::m_flag{};

5、C++11版本之后跨平台实现

std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
	Singleton* tmp = m_instance.load(std::memory_order_relaxed);
	std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
	if (tmp == nullptr) {
		std::lock_guard<std::mutex> lock(m_mutex);
		tmp = m_instance.load(std::memory_order_relaxed);
		if (tmp == nullptr) {
			tmp = new Singleton;
			std::atomic_thread_fence(std::memory_order_release);//释放内存fence
			m_instance.store(tmp, std::memory_order_relaxed);
		}
	}
	return tmp;
}

6、细心的会发现除4中其他的饿汉模式有new,但是没有delete,交给系统回收这段内存了,并不会产生什么问题。
通过代码回收的方式如下(未考虑多线程)。

在构造函数内创建类内静态对象CS,程序退出回收静态对象CS,利用CS的析构来释放单例。

class Singleton
{
private:
	class CSingleton
	{
	public:
		~CSingleton()
		{
			if (Singleton::m_instance)
			{
				delete Singleton::m_instance;
				Singleton::m_instance = nullptr;
			}
		}
	};
...
public:
	static Singleton* getInstance()
	{
		static CSingleton CS;
		return m_instance;
	}
};

四、总结

1、只能生成一个实例的类,实现了Singleton(单例)模式的类型。
2、条件1中具体要求表现为构造函数和析构函数私有,不允许有拷贝构造和拷贝函数。
3、由静态成员函数获取Singleton(单例)对象。
4、多线程安全。
5、若为饿汉式,创建单例对象后,不存在线程安全问题,考虑的性能问题。
6、reorder问题会导致经典双检查锁C++实现失效。

如有错误或不足欢迎评论指出!创作不易,转载请注明出处。如有帮助,记得点赞关注哦(⊙o⊙)
更多内容请关注个人博客:https://blog.csdn.net/qq_43148810

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大胡子的艾娃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值