【第七节】单例设计模式共享数据分析、解决、call_once
一、概谈设计模式
“设计模式”:代码的一些写法,程序灵活,维护起来比较方便,但是别人去接管代码,就比较困难;
设计模式不适合啥项目都往上面套,这就本末倒置了;
二、单例设计模式
单例设计模式一般的使用频率较高;
单例:整个项目中,有某个或者某些特殊的类,属于该类的对象,我只能创建一个,多了创建不了;
使用场景:在一个项目中,一个类由于各种原因只能生成一个类对象;
class MyCAS//单例类创建
{
private:
MyCAS(){}//构造函数私有化,之后不能直接使用 MyCAS A; 这种方法创建对象
private:
static MyCAS*m_instance;//静态成员变量,MyCAS类指针
public:
static MyCAS *GetInstance(){
if(m_instance==NULL)
m_instance=new MyCAS();//在这创建单例对象
return m_instance;
}
void func(){
cout<<"测试"<<endl;
}
~MyCAS(){
}
};
//类静态变量成员初始化
MyCAS *MyCAS::m_instance=NULL;
int main(){
MyCAS *p_a=MyCAS::GetInstance();//主线程中创建一个单例对象,返回该类对象的指针;
MyCAS *p_b=MyCAS::GetInstance();//此时实际上返回的还是上一个对象的指针,因此不会再创建一个对象
return 0;
}
如何释放new 的MyCAS对象?
这里使用的是在类中套类,释放对象的一种方法,这种方法有啥好处?老师没说;
除此之外还可以在析构函数中释放;
class MyCAS//单例类创建
{
private:
MyCAS(){}//构造函数私有化,之后不能直接使用 MyCAS A; 这种方法创建对象
private:
static MyCAS*m_instance;//静态成员变量,MyCAS类指针
public:
static MyCAS *GetInstance(){
if(m_instance==NULL)
{
m_instance=new MyCAS();//在这创建单例对象
static CgarHuiSHou cl;
}
return m_instance;
}
class CgarHuiSHou//类中套类,用来释放对象
{
public:
~CgarHuiSHou()//类的析构函数中
{
if(MyCAS::m_instance){
delete MyCAS::m_instance;
MyCAS::m_instance=NULL;
}
}
};
void func(){
cout<<"测试"<<endl;
}
};
//类静态变量成员初始化
MyCAS *MyCAS::m_instance=NULL;
int main(){
MyCAS *p_a=MyCAS::GetInstance();//创建一个对象,返回该类对象的指针;
MyCAS *p_b=MyCAS::GetInstance();//此时实际上返回的还是上一个对象的指针,因此不会再创建一个对象
return 0;
}
三、单例设计模式----多线程下共享数据问题分析、解决
问题:需要在自己创建的线程,即非主线程,中来创建MyCAS这个单例类的对象,这种线程可能不止一个;
于是就可能面临GetInstance() 这种成员函数需要互斥;
void *mythread(){
cout<<"我的线程开始执行了"<<endl;
MyCAS *p_a=MyCAS::GetInstance();
cout<<"我的线程结束了"<<endl;
return;
}
int main(){
std::thread mytobj1(mythread);//这两个线程都以mythread为入口函数,
std::thread mytobj2(mythread);//这是两个线程,即这里会有两条通路同时执行GetInstance()这个函数
mytobj1.join();
mytobj2.join();
return 0;
}
这两个线程都以mythread为入口函数,这是两个线程,即这里会有两条通路同时执行GetInstance()这个函数,于是便有可能,当线程一刚刚执行到:if(m_instance==NULL),马上就被切换到线程二,线程二由于m_instance还是NULL,也会满足if条件,继续执行下一步m_instance=new MyCAS();导致出现多个MyCAS对象,不满足单例条件;
解决:使用互斥量,来保证互斥访问GetInstance();
std::mutex my_mutex;//创建一个互斥量
class MyCAS//单例类创建
{
private:
MyCAS(){}//构造函数私有化,之后不能直接使用 MyCAS A; 这种方法创建对象
private:
static MyCAS*m_instance;//静态成员变量,MyCAS类指针
public:
static MyCAS *GetInstance(){
std::unique_lock<std::mutex> mymutex(my_mutex);//自动加锁
if(m_instance==NULL)
{
m_instance=new MyCAS();
static CgarHuiSHou cl;
}
return m_instance;
}
class CgarHuiSHou//类中套类,用来释放对象
{
public:
~CgarHuiSHou()//类的析构函数中
{
if(MyCAS::m_instance){
delete MyCAS::m_instance;
MyCAS::m_instance=NULL;
}
}
};
void func(){
cout<<"测试"<<endl;
}
};
但是使用这种方法,std::unique_lock<std::mutex> mymutex(my_mutex);//自动加锁 效率比较低,只是解决第一次加锁的问题,实际上第一个线程执行加锁new了一个对象之后,后面的线程都不需要在经历加锁再判断这个流程;
使用双重锁定,可以优化:N个线程调用GetInstance()函数之后,一个或者小于N个线程(小于N的情况可能都比较少,这种情况是假设一个线程在执行完第一个if(m_instance==NULL)之后,马上又切换到其他线程)的m_instance是NULL,但是有且仅有一个线程可以加锁 且new对象,等他退出之后,其他线程进入这个函数之后m_instance已经不等于NULL,就不需要在经历加锁解锁的过程,而是直接返回;
static MyCAS *GetInstance(){
if(m_instance==NULL){//使用双重锁定(双重检查)这种方法提高效率
std::unique_lock<std::mutex> mymutex(my_mutex);//自动加锁
if(m_instance==NULL)
{
m_instance=new MyCAS();
static CgarHuiSHou cl;
}
}
return m_instance;
}
四、std::call_once():C++11引入的函数,该函数的第二个参数是一个函数名a()
call_once()功能是保证函数a()只被调用一次;
call_once具备互斥量的功能,而且效率上比互斥量消耗的资源少;
call_once()需要与一个标记结合使用,这个标记std::once_flag,其实是一个结构;
call_once()就是通过这个标记来决定对应的函数a()是否执行,调用call_once()成功之后,这个标记就会被设置为已调用状态,后续再次调用call_once之后,检查once_flag是否是已调用状态,若已调用,则对应的函数a()就不会再执行了;
改写程序:假设两个线程同时执行到call_once,其中一个线程要等待另外一个线程执行完毕CreatInstance(),然后检查g_flag的状态来决定是否执行函数,此时g_flag的作用就相当于一个互斥量。
using namespace std;
std::mutex my_mutex;//创建一个互斥量
std::once_flag g_flag;//创建一个标记
class MyCAS//单例类创建
{
static void CreatInstance(){//只能被调用一次的函数
m_instance=new MyCAS();
static CgarHuiSHou cl;
}
private:
MyCAS(){}//构造函数私有化,之后不能直接使用 MyCAS A; 这种方法创建对象
private:
static MyCAS*m_instance;//静态成员变量,MyCAS类指针
public:
static MyCAS *GetInstance(){
std::call_once(g_flag,CreatInstance);//假设两个线程同时执行到这里,其中一个线程要等另外一个线程执行完毕CreatInstance(),此时g_flag的作用就相当于一个互斥量
return m_instance;
}
class CgarHuiSHou//类中套类,用来释放对象
{
public:
~CgarHuiSHou()//类的析构函数中
{
if(MyCAS::m_instance){
delete MyCAS::m_instance;
MyCAS::m_instance=NULL;
}
}
};
void func(){
cout<<"测试"<<endl;
}
};
//类静态变量成员初始化
MyCAS *MyCAS::m_instance=NULL;
void *mythread(){
cout<<"我的线程开始执行了"<<endl;
MyCAS *p_a=MyCAS::GetInstance();
cout<<"我的线程结束了"<<endl;
return;
}
int main(){
std::thread mytobj1(mythread);//这两个线程都以mythread为入口函数,
std::thread mytobj2(mythread);//这是两个线程,即这里会有两条通路同时执行GetInstance()这个函数,
mytobj1.join();
mytobj2.join();
return 0;
}
建议一般最好在主线程中就把单例对象创建了,避免在子线程中创建而导致出现问题;
MyCAS *p_a=MyCAS::GetInstance();//主线程中创建一个单例对象,返回该类对象的指针;
//然后再创建子线程,直接就可以在子线程中使用这个单例对象
std::thread mytobj1(mythread);//这两个线程都以mythread为入口函数,
std::thread mytobj2(mythread);
本文介绍了C++中的单例设计模式,用于确保类只有一个实例并提供全局访问点。讨论了单例模式在多线程环境下的问题,如线程安全和数据共享,并提出了使用互斥量和`std::call_once()`函数来解决并发访问的方案。文章还强调了在主线程中预先创建单例以避免子线程创建时可能出现的问题。

被折叠的 条评论
为什么被折叠?



