C++并发与多线程(六) 单例设计模式、call_once

单例设计模式

单例:整个项目中,有某个或者某些特殊的类,属于该类的对象,只能创建一个,不允许创建多个。

单例模式实现方法

//单例类
class MyCAS {
private:
	//构造函数私有化,就不可以实例化对象了
	MyCAS() {};
private:
	static MyCAS *m_instance; //静态成员变量,用于实例化单例类
public:
	//提供的实例化对象接口
	static MyCAS *GetInstance() {
		if (m_instance == NULL) {
			m_instance = new MyCAS();
		}
		return m_instance;
	}

	//测试函数
	void func() {
		cout << "func的测试" << endl;
	}

};
//静态成员必须类外初始化
MyCAS *MyCAS::m_instance = NULL;


int main() {

	MyCAS *p_a = MyCAS::GetInstance(); //实例化一个对象,返回类(MyCAS)的指针
	MyCAS *p_b = MyCAS::GetInstance(); //再次实例化无效,两次返回的是同一个指针

	return 0;
}
  1. 构造函数私有化:private: MyCAS();
  2. 提供静态成员变量,存放实例化单例类的指针static MyCAS *m_instance;
  3. 提供实例化对象接口static MyCAS *GetInstance(),重要的是只有当m_instance == NULL的时候才会new 一个新对象,这样才能保证只能实例化一个对象。

单例模式下的delete

之前的单例类会new MyCAS,相应的我们必须要对其进行delete,这里是一种类中嵌套类,利用类的析构函数来释放空间的方法:

public:
	//提供的实例化对象接口
	static MyCAS *GetInstance() {
		if (m_instance == NULL) {
			m_instance = new MyCAS();
			static GarbageCollection gc; //定义一个静态变量,生命周期是整个程序的生命周期
		}
		return m_instance;
	}

	class GarbageCollection { //类中套类,用来释放对象
		//在析构函数中释放对象
	public:
		~GarbageCollection() {
			if (MyCAS::m_instance) {
				delete MyCAS::m_instance;
				MyCAS::m_instance = NULL;
			}
		}
	};
  1. 类中嵌套类,定义一个class GarbageCollection,在这个类的析构函数中,调用delete。
  2. MyCAS::GetInstance中定义一个GarbageCollection类的静态变量static GarbageCollection gc,这里定义静态变量的原因是,静态变量的生命周期是整个程序的生命周期,当程序运行结束的时候,自动会调用他的析构函数释放掉我们申请的空间。

单例设计模式下的共享数据问题

在单例模式下,假设需要在我们自己创建的线程(而不是主线程中)来创建MyCAS这个单例类的对象,这种线程可能不止一个(最少两个),这时,我们需要面临GetInstance()这种成员函数需要互斥

//线程入口函数
void myThread() {
	cout << "我的线程开始执行" << endl;
	MyCAS *p_a = MyCAS::GetInstance(); //实例化MyCAS对象,会出现访问冲突的问题
	cout << "我的线程执行结束" << endl;
}


int main() {
	//创建两个线程都去实例化MyCAS对象
	thread mythread1(myThread);
	thread mythread2(myThread);

	mythread1.join();
	mythread2.join();


	return 0;
}

解决方案:

GetInstance()中加一个互斥量即可:

static MyCAS *GetInstance() {
		unique_lock<mutex> mymutex(mymutex); //加一个锁
		if (m_instance == NULL) {
			m_instance = new MyCAS();
			static GarbageCollection gc; //定义一个静态变量,生命周期是整个程序的生命周期
		}
		return m_instance;
	}

但这又带来一个问题,每次调用MyCAS类中成员函数,不可避免的要调用GetInstance获取对象指针,那么每次调用都要加锁,这样效率很低,所以我们要继续修正:

static MyCAS *GetInstance() {
		//加锁
		if (m_instance == NULL) { //双重锁定(双重检查)
			unique_lock<mutex> mymutex(mymutex);
			if (m_instance == NULL) {
				m_instance = new MyCAS();
				static GarbageCollection gc; //定义一个静态变量,生命周期是整个程序的生命周期
			}
		}
		return m_instance;
	}

在外层又嵌套了一个if判断,这就是双重锁定,可以提高效率

  1. 如果if (m_instance != NULL)条件成立,则肯定表示m_instance已经被new过;
  2. 如果if (m_instance == NULL)条件成立,不代表m_instance一定没被new过,可能是多线程导致还没来及的赋值,就切换了上下文;

PS:这里一定要避免单线程思维,直接顺着理两次if的逻辑,要用多线程思维,程序执行到任意位置都可能被抢占!(双检查锁似乎会带来内存泄露的问题,日后用到时可以查阅一下)

call_once()

call_once()能够保证函数a()只被调用一次。具备互斥量的能力,而且比互斥量消耗的资源更少,更高效。

call_once()的第一个参数是一个标记once_flag,第二个参数是函数名a;

这个标记为std::once_flag;其实once_flag是一个结构,call_once()就是通过标记来决定函数是否执行,调用成功后,就把标记设置为一种已调用状态。

我们用call_once解决上面提出的单例模式中的GetInstance访问可能冲突的问题:

//once_flag标记
once_flag g_flag;
//单例类
class MyCAS {
private:
	static void CreateInstance(){ //因为call_once第二个参数只能是函数名,要把代码封装到函数里
		if (m_instance == NULL) {
			m_instance = new MyCAS();
			static GarbageCollection gc; //定义一个静态变量,生命周期是整个程序的生命周期
		}
	}
public:
	//提供的实例化对象接口
	static MyCAS *GetInstance() {
		//功能已经被封装到CreateInstance函数中
		call_once(g_flag, CreateInstance);
		return m_instance;
	}
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值