zoukankan      html  css  js  c++  java
  • C++11 单例类实现

    单例类:

    (1) 单例类保证全局只有一个唯一的实例对象。

    (2) 单例类保证只有唯一的接口获取这唯一实例。

    非线程安全的单例类举例:

     1 class CSingleton
     2 {
     3 public:
     4     ~CSingleton(){}
     5     static CSingleton * getInstance()
     6     {
     7         if (m_instance == nullptr)
     8         {
     9             m_instance = new CSingleton;
    10         }
    11         return m_instance;
    12     }
    13     static void delInstance()
    14     {
    15         if (m_instance)
    16         {
    17             delete m_instance;
    18             m_instance = nullptr;
    19         }
    20     }
    21     void print()
    22     {
    23         std::cout << "print test" << std::endl;
    24     }
    25 private:
    26     CSingleton(){}
    27     CSingleton & operator=(const CSingleton & ) = delete;
    28     CSingleton(const CSingleton &) = delete;
    29 private:
    30     static CSingleton * m_instance;
    31 };
    32 
    33 CSingleton * CSingleton::m_instance = nullptr;

    上述单例类面对多线程并发访问时会出错。

    看如下线程安全的单例类(非C++11实现)

     1 class CSingleton
     2 {
     3 public:
     4     ~CSingleton() {}
     5     static CSingleton * getInstance()
     6     {
     7         if (m_instance == nullptr)
     8         {
     9             std::lock_guard<std::mutex> lgd(m_mt);
    10             if (m_instance == nullptr)
    11             {
    12                 m_instance = new CSingleton;
    13             }
    14         }
    15         return m_instance;
    16     }
    17     static void delInstance()
    18     {
    19         std::lock_guard<std::mutex> lgd(m_mt);
    20         if (m_instance)
    21         {
    22             delete m_instance;
    23             m_instance = nullptr;
    24         }
    25     }
    26     void print()
    27     {
    28         std::cout << "print test" << std::endl;
    29     }
    30 private:
    31     CSingleton() {}
    32     CSingleton & operator=(const CSingleton & ) = delete;
    33     CSingleton(const CSingleton &) = delete;
    34 private:
    35     static CSingleton * m_instance;
    36     static std::mutex m_mt;
    37 };
    38 
    39 CSingleton * CSingleton::m_instance = nullptr;
    40 std::mutex CSingleton::m_mt;

    当然绝对的线程安全还是有问题,因为C++创建对象时,会执行1、分配内存,2 调用构造,3 赋值操作三步操作,然而现代CPU和编译器高并发下可能

    会进行乱序重排操作,因而创建对象new CSingleton的第2步可能会晚于第3步进行指令调用,因而导致出现未定义的的行为。

    举例:

    线程A : getInstance 判断 instance是否为空,为空则

    线程A : 分配内存  此时CPU乱序指令重排,赋值操作提前

    线程B : getInsnace 判断instance是否为空,非空,则返回

    线程B : 使用了未初始化的instacne 出现未定义行为。

    线程A : 调用构造函数对instance初始化。

    因此要解决上述问题需要引入内存栅栏来确保指令运行的同步性。在CPU指令重排的前提下保持数据的一致性。

    C++11支持线程安全的单例类:

    C++11的单例模式的实现

     1 class CSingleton
     2 {
     3 public:
     4     ~CSingleton() {}
     5     static CSingleton & getInstance()
     6     {
     7         static CSingleton m_instance;
     8         return m_instance;
     9     }
    10     void print()
    11     {
    12         std::cout << "print test" << std::endl;
    13     }
    14 };

    返回静态局部对象的引用,C++11中是线程安全的。

    验证一下:

     1 class CStatic
     2 {
     3 public:
     4     CStatic()
     5     {
     6         std::cout << "construct begin" << std::endl;
     7         Sleep(5000);
     8         std::cout << "construct end" << std::endl;
     9     }
    10     void print()
    11     {    
    12         std::cout << "print" << std::endl;
    13         std::cout << s_num++ << std::endl;
    14     }
    15     static int s_num;
    16     static std::mutex s_mt;
    17 };
     1 int CStatic::s_num = 0;
     2 std::mutex CStatic::s_mt;
     3 
     4 //
     5 void thread_func()
     6 {
     7     static CStatic st;
     8     st.print();
     9 }
    10 
    11 int main()
    12 {
    13     std::vector<std::thread> vecThread;
    14     for (auto i = 0; i< 8; i++)
    15     {
    16         vecThread.push_back(std::thread(thread_func));
    17     }
    18     for (auto i = 0; i< 8; i++)
    19     {
    20         vecThread[i].join();
    21     }
    22     //
    23     system("pause");
    24     return 0;
    25 }

    首先我们创建一个CStatic类,然后创建8个线程来启动thread_func(),thread_func()初始化了一个静态CStatic对象,(静态局部变量仅被初始化一次)

    然后接着运行。我们发现,当首个线程初始化CStatic时,其他线程都是被阻塞的,从构造函数的begin和end中可以看到,我们故意让其停留5s,

    如下图,其他线程都是在st被初始化之后才运行。

    所以CStatic静态局部对象被构造的过程中是线程安全的,但是其拥有的成员变量则不是线程安全的。

    因此我们增加个简单的锁,

     1 class CStatic
     2 {
     3 public:
     4     CStatic()
     5     {
     6         std::cout << "construct begin" << std::endl;
     7         Sleep(5000);
     8         std::cout << "construct end" << std::endl;
     9     }
    10     void print()
    11     {    
    12         std::lock_guard<std::mutex> lgd(s_mt);
    13         std::cout << "print" << std::endl;
    14         std::cout << s_num++ << std::endl;
    15     }
    16     static int s_num;
    17     static std::mutex s_mt;
    18 };

     

  • 相关阅读:
    Java
    Spring
    Q&A
    Q&A
    Q&A
    Spring
    Elasticsearch部署及基本概念
    rust(二) 变量及类型
    rust(一) 一些命令
    vim笔记
  • 原文地址:https://www.cnblogs.com/Forever-Kenlen-Ja/p/7050300.html
Copyright © 2011-2022 走看看