关于单件模式

单件模式是一种设计模式,确保一个类只有一个实例,并提供全局访问点。实现方式包括私有化构造函数、静态成员函数。类型分为饿汉模式(类加载时创建实例)和懒汉模式(首次调用时创建)。线程安全的懒汉模式通常需要加锁,如使用互斥锁。考虑内存效率,可在主线程初始化单件实例以避免线程锁。
摘要由CSDN通过智能技术生成

什么是单件模式

一个类只有一个实例,并为之提供一个全局的访问点。

如何实现

把类的构造函数私有化,禁用类的拷贝构造函数、赋值函数和移动构造函数。

并且提供一个全局访问静态成员函数,使外部可以直接获取或初始化实例。

class Singleton
{
	public:
		~Singleton(){}

		static Singleton* getInstance()
		{
			if (nullptr == m_pInstance)
			{
				m_pInstance = new Singleton();
			}
			return m_pInstance;
		}
		static void delMe()
		{
			if (nullptr != m_pInstance)
			{
				delete m_pInstance;
				m_pInstance = nullptr;
			}
		}

	private:
		Singleton(){}
		Singleton(Singleton& other) = delete;
		Singleton(Singleton&& other) = delete;
		Singleton& operator=(Singleton& other) = delete;

	private:
		static Singleton* m_pInstance;
};

Singleton* Singleton::m_pInstance = nullptr;

以上代码是一个单件模式的简单实现,当然这个实现在多线程下并不安全。

单件模式的类型

我们根据实例构建的时机将单件模式分为两类:

饿汉模式

在类加载时已经创建好实例。

将上面代码的最后一行改成以下代码即可。

优点:实现简单,因为实例是在main执行之前初始化的,所以调用时不需要初始化实例,线程安全。

缺点:同样因为实例是在main执行前初始化,所以影响main的执行速度,而且会提前占用内存。

无法控制实例的构造顺序以及依赖关系。

// 直接初始化静态成员
Singleton* Singleton::m_pInstance = new Singleton();

懒汉模式

在第一次需要调用的时候,才创建实例。

以上简单实现的代码即为懒汉模式。

优点:在首次需要调用的时候初始化,不会造成内存浪费。可以精准控制初始化的顺序和依赖关系。

缺点:非线程安全,多线程调用需要加锁,影响性能。

线程安全

template <class T>
class Singleton
{
	private:
		const Singleton& operator=(const Singleton&);
		Singleton(const Singleton&);

	protected:
		Singleton()
		{
			cout << "Singleton constructor" << endl;
		}

		~Singleton()
		{
			cout << "Singleton desconstructor" << endl;
		}

		static T* m_pInstance;
		/// 多线程用
		static mutex m_mutex;

	public:
		static T* getMe()
		{
			if (nullptr == m_pInstance)
			{
				m_mutex.lock();
				if (nullptr == m_pInstance)
				{
					m_pInstance = new T();
				}
				m_mutex.unlock();
			}
			return m_pInstance;
		}

		static void delMe()
		{
			if (nullptr != m_pInstance)
			{
				delete m_pInstance;
				m_pInstance = nullptr;
			}
		}
};

template <class T>
T* Singleton<T>::m_pInstance = nullptr;
template <class T>
mutex Singleton<T>::m_mutex;

以上为线程安全的懒汉模式。

也可以通过pthread_once函数来实现线程安全代码,原理和上面代码差不多。

补充:

static T* getMe()
		{
			if (nullptr == m_pInstance)
			{
				m_mutex.lock();
				if (nullptr == m_pInstance)
				{
					m_pInstance = new T();
				}
				m_mutex.unlock();
			}
			return m_pInstance;
		}

关于这段代码中,在读程序员的自我修养(P29)时发现存在问题。问题来源于CPU的乱序执行,代码中的m_pInstance = new T();  包含三个步骤:

  1. 分配内存。
  2. 在内存位置上调用构造函数。
  3. 将内存地址赋给m_pInstance。

其中2、3的顺序是可以颠倒的。所以可能出现一种情况:m_pInstance的值已经不为nullptr,但对象的构造函数还没有完成,这显然不是我们想要看到的情况。

这里想要保证线程安全,必须阻止CPU换序,可以通过barrier指令来实现。代码如下:

#define barrier() __asm__ volatile ("lwsync")
static T* getMe()
		{
			if (nullptr == m_pInstance)
			{
				m_mutex.lock();
				if (nullptr == m_pInstance)
				{
				    T* temp = new T();
                    barrier(); // 保证线程安全
                    m_pInstance = temp;
				}
				m_mutex.unlock();
			}
			return m_pInstance;
		}

写在最后

如果对内存要求没那么高,可以在多线程未创建时,用懒汉模式在主线程初始化单件实例,以避免使用线程锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值