• C++并发与多线程学习笔记--单例设计模式、共享数据分析


    • 设计模式
    • 共享数据分析
    • call_once

    设计模式

    开发程序中的一些特殊写法,这些写法和常规写法不一样,但是程序灵活,维护起来方便,别人接管起来,阅读代码的时候都会很痛苦。用设计模式理念写出来的代码很晦涩,国内的05~10年的时候有一本“Head First”,写程序的时候谈到设计模式。

    项目开发经验+模块开发经验=设计模式

    先有开发需求,然后把一个大的工程拆分很很多小的模块,然后演变出设计模式。当设计模式传到国内来的时候,很多程序员写代码把设计模式往代码中套,使得一个很小的程序的变得复杂,而且源码晦涩,本来设计模式是为了把大的东西拆成小的东西,编程的时候,各个模块方便管理。而不是生搬硬套,写程序的时候要“活学活用”。

    单例设计模式,使用的频率比较高:在整个项目中,有某个或者某些特殊的类,属于该类的对象,我只能创建一个,多了创建不了。单例是写法比较特殊的类,整个项目中只能有一个,用于配置文件之类的操作。

    单例类;

    class MyCAS //这是一个单例类
    {
    private:
        MyCAS(){}           //构造函数私有化了
                                   //不能创建对象
    private:
        static MyCAS *m_instance; //静态成员变量
    
    public:
        static MyCAS *GetInstance()
        {
             if(m_instance ==nullptr)
                 { 
                     m_instance = new MyCAS();
                  } 
              return m_instance;
        }
    };
    

     在main函数之外,类静态变量初始化

    MyCAS *MyCAS::m_instance = nullptr;     //静态变量的初始化
    

     在main函数内

    MyCAS *p_a = MyCAS::GetInstance(); //创建一个对象,返回该类对象的指针
    

        如果再写 MyCAS *p_b = MyCAS::GetInstance(),此时仍然指向同一个实例,返回唯一的对象指针。

    析构函数写法,此时还有析构代码要写上,在原来的类中定一个新类,类中套类,用来释放对象。

    public:
        static MyCAS *GetInstance()
        {
             if(m_instance ==nullptr)
                 { 
                     m_instance = new MyCAS();
                     static  GarRelease cl;                   //程序退出的时候析构这个类,自动释放资源
                  } 
              return m_instance;
        }
    
       class GarRelease
       {
              ~GarRelease()
              {
                  if(MyCAS::m_instance)
                 { 
                     delete MyCAS::m_instance;
                     MyCAS::m_instance = nullptr;
                 }
              }
        };
    

      小结:因为把构造函数私有化了,不能通过对象直接生成对象,只能通过一个static来生成,并且构造指针指向同一个对象,整个类的写法就是一种设计模式。

    共享数据分析

    单例模式会面临一个问题,GetInstance被多个线程使用,如果一个数据是只读的,多个线程之间不需要互斥,但是有一个问题,单例对象创建的时候,在创建之前把对象初始化,载入数据,后续只读。

    实际中可能面临的问题:需要我们在自己创建的线程中创建单例类,这种线程不止一个。

    可能需要面临GetInstance()需要互斥的情况:

    如果两个线程用同一个入口函数:

    void myThread()
    {
        MyCAS *p_a = MyCAS::GetInstance();
    }
    

      会出现问题:两个线程是同一个入口函数,但是这个两个线程,不管用的是哪个入口函数,两个线程意味着两个流程(通路),同时开始执行这个函数,此时需要一个互斥量防止多个线程同时调用GetInstance()函数

    std::unique_lock<std::mutex> mymutex(resource_mutex); //自动加锁,出了作用域之外自动解锁
    

      程序写完,还是会被怼,程序写完里面有很多地方都要调用GetInstane(),拿到对象指针,如果调用程序很频繁使用,效率非常低。在最外面一层包裹一个判断条件,如果:

    a)if(m_instance!=nullptr) 条件成立,则肯定表示m_instance 已经被new过了;

    b)if(m_instance ==nullptr) 条件成立,不代表m_instance 一定没被new过;

    c)双重锁定;

        static MyCAS *GetInstance()
        {
             if(m_instance ==nullptr)
             { 
                std::unique_lock<std::mutex> mymutex(source_mutex)
                if(m_instance ==nullptr)
                 { 
                     m_instance = new MyCAS();
                     static  GarRelease cl;                  
                  }                
              } 
              return m_instance;
        }
    

      

    call_once

    std::call_once()是一个函数模板,这个也是C++11引入的函数,其中第二个参数,就是一个函数名,第二个参数就是一个函数名a(),call_once()能够保证函数a只被调用一次,比如说有两个线程,都调用了函数a,正常情况下被调用了两次,如果用call_once,就能保证函数只被调用了一次,如果把核心的共享数据代码,(new 一个对象)。

    单例对象在多线程的情况下,初始化需要mutex,call_once具备互斥量这种能力,而且效率上比互斥量消耗的资源更少。call_once需要与一个标记结合使用,std::once_flag,其实这是一个结构,就可以看成一个标记。通过这个标记来决定对应的函数是否执行,调用call_once成功后,call_once就把这个标记设置为已调用的状态,后续再次调用call_once(),只要标记被设置为“已调用”状态,那么对应的函数a就不会在被执行了。

    std::once_flag g_flag;   //这是个系统定义的标记
    

     完整的写法

    class MyCAS
    {
        static void CreateInstance(); //只被调用一次的函数
         {
              m_instance = new MyCAS();
              static GarRelease cl;
         }
         static MyCAS *GetInstance()
        {
    
              std::call_once(g_flag, CreateInstance);
              return m_instance;
        }
    
    }
    

      假设两个线程都同时开始执行GetInstance(),同时执行到std::call_once(),call_once就好像一个锁,其中一个线程调用,另一个线程就要等当前这个线程执行完毕,才会去决定是否调用CreateInstance(),此时的call_once的标记已经被改变。

    参考文献

    https://study.163.com/course/courseLearn.htm?courseId=1006067356#/learn/video?lessonId=1053491360&courseId=1006067356

  • 相关阅读:
    Spark Mllib里的向量标签概念、构成(图文详解)
    Spark Mllib里的本地向量集(密集型数据集和稀疏型数据集概念、构成)(图文详解)
    Spark Mllib里的Mllib基本数据类型(图文详解)
    [转]Debugging into .NET Core源代码的两种方式
    [转]在ASP.NET Core使用Middleware模拟Custom Error Page功能
    [转]Asp.net MVC中的ViewData与ViewBag
    [转]细说 ASP.NET Cache 及其高级用法
    [转]分布式中使用Redis实现Session共享(二)
    [转]Asp.net Core 使用Redis存储Session
    [转]JS跨域解决方式 window.name
  • 原文地址:https://www.cnblogs.com/rynerlute/p/11829520.html
Copyright © 2020-2023  润新知