C++11并发与多线程笔记(7) 单例设计模式共享数据分析、解决,call_once

第七节 单例设计模式共享数据分析、解决,call_once

在这里插入图片描述

1.设计模式

  • 程序灵活,维护起来可能方便,用设计模式理念写出来的代码很晦涩,但是别人接管、阅读代码都会很痛苦
  • 用“设计模式”理念写出来的代码是很晦涩的;《head first》
  • 老外应付特别大的项目时,把项目的开发经验、模块划分经验,总结整理成设计模式
  • 中国零几年设计模式刚开始火时,总喜欢拿一个设计模式往上套,导致一个小小的项目总要加几个设计模式,本末倒置
  • 设计模式有其独特的优点,要活学活用,不要深陷其中,生搬硬套
  • 这些年设计模式说的少了,没那么火了

2.单例设计模式:

  • 整个项目中,有某个或者某些特殊的类,只能创建一个属于该类的对象。
  • 单例类:只能生成一个对象。
  • 如果觉得在单例模式new了一个对象,而没有自己delete掉,这样不合理。可以增加一个类中类CGarhuishou,new一个单例类时创建一个静态的CGarhuishou对象,这样在程序结束时会调用CGarhuishou的析构函数,释放掉new出来的单例对象。
#include<iostream>
using namespace std;
//单例类设计模式
class MyCAS
{
private:
	MyCAS() {}//私有化了构造函数
private:
	static MyCAS *m_instance;//静态成员变量
public:
	static MyCAS *Getinstance()
	{
		if (m_instance == NULL)
		{
			m_instance = new MyCAS();
			static CGarhuishou cl;//在析构cl对象时,来delete m_instance对象
		}
		return m_instance;
	}
	class CGarhuishou //类中套类,用来释放对象
	{
	public:
		~CGarhuishou()//类的析构函数中delete
		{
			if (MyCAS::m_instance)
			{
				delete MyCAS::m_instance;
				MyCAS::m_instance = NULL;
			}
		}
	};
	void func()
	{
		cout << "测试" << endl;
	}
};
//类静态变量初始化在类外面,如果静态成员是const的,则可以在类声明中初始化
MyCAS *MyCAS::m_instance = NULL;
int main()
{
	//MyCAS a;//私有化了构造函数,就不能这样创建类对象了,只能调用Getinstrance()接口
	MyCAS *p_a = MyCAS::Getinstance();//创建一个对象,返回该类(MuCAS)对象的指针
	MyCAS *p_b = MyCAS::Getinstance();//m_instance不为空,直接返回,也指向上个对象
	//这两个返回的是唯一的单例类指针
	p_a->func();
	MyCAS::Getinstance()->func();

	return 0;
}

3.单例设计模式共享数据分析、解决

  • 在主线程中创建MyCAS单例类对象,该装载的变量装载好(变量都是只读的),这样就不存在共享数据问题。
  • 面临问题:需要在自己创建的线程中来创建单例类的对象,这种线程可能不止一个(至少2个)。这时我们可能面临GetInstance()这种成员函数需要互斥。
  • 可以在加锁前判断m_instance是否为空,否则每次调用Singelton::getInstance()都要加锁,十分影响效率。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

std::mutex resource_mutex;
//单例类
class MyCAS
{
private:
	MyCAS() {}//私有化了构造函数
private:
	static MyCAS *m_instance;//静态成员变量
public:
	static MyCAS *Getinstance()
	{
		//a)如果if (m_instance != NULL) 条件成立,则肯定表示m_instrance已经被new过了
		//b) 如果if (m_instance == NULL),且没有加锁,不代表m_instrance没被new过,可能另一个线程new的
		if (m_instance == NULL)//双重锁定(双重检查) 高效  能保证不用每次调用都加锁,仅在初始化时加锁
		{
			std::unique_lock<std::mutex> mymutex(resource_mutex);//自动加锁 仅在第一次创建时才有意义 低效
			if (m_instance == NULL)
			{
				m_instance = new MyCAS();
				static CGarhuishou cl;//在析构cl对象时,来delete m_instance对象
			}
		}
		return m_instance;
	}
	class CGarhuishou //类中套类,用来释放对象
	{
	public:
		~CGarhuishou()//类的析构函数中delete
		{
			if (MyCAS::m_instance)
			{
				delete MyCAS::m_instance;
				MyCAS::m_instance = NULL;
			}
		}
	};
	void func()
	{
		cout << "测试" << endl;
	}
};
//类静态变量初始化在类外面,如果静态成员是const的,则可以在类声明中初始化
MyCAS *MyCAS::m_instance = NULL;
//线程入口函数
void mythread()
{
	cout << "我的线程开始了" << endl;
	MyCAS *p_a = MyCAS::Getinstance();//这里就可能有问题了
	cout << "我的线程执行完了" << endl;
	return;
}
int main()
{
	//虽然两个线程是同一个入口函数,但这是两个线程,所以这里有两条通路同时执行mythread函数
	//可能两个线程都new了一个MyCAS的对象
	std::thread myobj1(mythread);
	std::thread myobj2(mythread);
	myobj1.join();
	myobj2.join();

	return 0;
}

4.std::call_once():

  • C++11引入的函数模板,该函数的第一个参数为标记,第二个参数是一个函数名(如a())。
  • 功能:能够保证函数a()只被调用一次。具备互斥量的能力,而且比互斥量消耗的资源更少,更高效(待测试)。
  • call_once()需要与一个标记结合使用,这个标记为std::once_flag;其实once_flag是一个结构,call_once()就是通过这个标记来决定对应的函数是否执行,调用a()成功后,就把标记位设置为一种已调用状态。后续再次调用call_once,只要once_flag是已调用状态,那么对应的函数就a()就不会再被调用了
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

std::once_flag g_flag;//现在这是个系统定义的标记
//单例类
class MyCAS
{
	static void CreatInstance()
	{
		std::chrono::milliseconds dura(20000);//休息20s 调试看结果用
		std::this_thread::sleep_for(dura);
		cout << "CreatInsrtance()被执行了" << endl;
		m_instance = new MyCAS();
		static CGarhuishou cl;
	}
private:
	MyCAS() {}//私有化了构造函数
private:
	static MyCAS *m_instance;//静态成员变量
public:
	static MyCAS *Getinstance()
	{
		std::call_once(g_flag, CreatInstance);//两个线程同时执行到这里,其中一个线程会等另一个线程执行完毕CreatInstance
		//可以把g_flag看成一把锁 第一个线程调用calll_once更改了g_flag标记,保证CreatInstance只会被执行一次
		cout << "call_once执行完毕" << endl;
		return m_instance;
	}
	class CGarhuishou //类中套类,用来释放对象
	{
	public:
		~CGarhuishou()//类的析构函数中delete
		{
			if (MyCAS::m_instance)
			{
				delete MyCAS::m_instance;
				MyCAS::m_instance = NULL;
			}
		}
	};
	void func()
	{
		cout << "测试" << endl;
	}
};
//类静态变量初始化在类外面,如果静态成员是const的,则可以在类声明中初始化
MyCAS *MyCAS::m_instance = NULL;
//线程入口函数
void mythread()
{
	cout << "我的线程开始执行了" << endl;
	MyCAS *p_a = MyCAS::Getinstance();//这里就可能有问题了
	cout << "我的线程执行完毕了" << endl;
	return;
}
int main()
{
	//虽然两个线程是同一个入口函数,但这是两个线程,所以这里有两条通路同时执行mythread函数
	//可能两个线程都new了一个MyCAS的对象
	std::thread myobj1(mythread);
	std::thread myobj2(mythread);
	myobj1.join();
	myobj2.join();

	return 0;
}

建议:在主线程中将单例对象创建出来,该初始化的初始化,然后再创建子线程,就不会出现在子线程里多次new单例对象的情况了。双重锁定效率可能更好一点,推荐使用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值