c++11并发与多线程【第七节】单例设计模式共享数据分析、解决、call_once

【第七节】单例设计模式共享数据分析、解决、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++并发与多线程视频课程》

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2024 lk
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信