C++线程安全的单例模式

1,001 阅读3分钟

引言

单例模式是在工程上比较常见的设计模式,顾名思义,单例是指在程序运行周期内有且只有一个实例化的对象。在面试过程中,实现线程安全的单例模式也是个高频考点,本文也是立足于面试,罗列C++的线程安全单例模式的实现方法。

单例的实现方式

根据实例化的时机,主要分为饿汉和懒汉两种。

  1. 饿汉。第一次获取实例前就已经创建好。
    class Singleton {
     public:
      static Singleton* GetInstance();
     private:
      static Singleton* instance_;
    };
    Singleton* instance_ = new Singleton();  // 已经创建好
    
    Singleton* Singleton::GetInstance() {
      return instance_;
    }
    
  2. 懒汉。第一次获取实例时才创建。
    class Singleton {
     public:
      static Singleton* GetInstance();
     private:
      static Singleton* instance_;
    };
    Singleton* instance_ = nullptr;  // 静态变量的初始化,默认为nullptr
    
    Singleton* Singleton::GetInstance() {
      if (instance_ == nullptr) {
        instance_ = new Singleton();  // instance_为空时才创建
      }
      return instance_;
    }
    

线程安全

线程安全问题简单来说就是多线程在对同一共享对象的读写不一致。这里要声明一点,针对单例模式的线程安全是指获取实例这一操作,而不是对获取到的实例对象本身的操作。下面来分析一下上述两种实现方式的线程安全性:

  • 在饿汉的方式下,调用GetInstance()将直接返回已经创建好的实例,可以认为都是读操作,因此是线程安全的;
  • 在懒汉的方式下,可以看到GetInstance()的注释里说的是“intance_为空时才创建”,而不是“第一次获取实例时才创建”,对于前者一种可能的问题就是两个线程在都进入到了if (instance_ == nullptr)的判断分支里,都执行了instance_ = new Singleton()这一步,结果显而易见,Singleton被实例化两次,这样就导致了线程不安全。

线程安全的懒汉单例模式

既然找到了线程不安全的原因,下面直接给出安全的例子:

  1. 加互斥锁
    class Singleton {
     public:
      static Singleton* GetInstance();
     private:
      static Singleton* instance_;
      static mutex mtx_;
    };
    Singleton* Singleton::instance_ = nullptr;  // 静态变量的初始化,默认为nullptr
    mutex Singleton::mtx_;
    
    Singleton* Singleton::GetInstance() {
      unique_lock<mutex> lock(mtx_);
      if (instance_ == nullptr) {
        instance_ = new Singleton();  // instance_为空时才创建
      }
      return instance_;
    }
    
  2. 双重检查锁(DCL)
    class Singleton {
     public:
      static Singleton* GetInstance();
     private:
      static Singleton* instance_;
      static mutex mtx_;
    };
    Singleton* Singleton::instance_ = nullptr;  // 静态变量的初始化,默认为nullptr
    mutex Singleton::mtx_;
    
    Singleton* Singleton::GetInstance() {
      if (instance_ == nullptr) {
        unique_lock<mutex> lock(mtx_);
        if (instance_ == nullptr) {
          instance_ = new Singleton();  // instance_为空时才创建
        }
      }
      return instance_;
    }
    
  3. 内部静态变量
    class Singleton {
     public:
      static Singleton* GetInstance();
    };
    
    Singleton* Singleton::GetInstance() {
      static Singleton instance;  // !指定C++11编译选项
      return &instance;
    }
    
  4. unique_lock+call_once
    class Singleton {
     public:
      static Singleton& GetInstance();
     private:
      static unique_ptr<Singleton> instance_;
    };
    
    Singleton& Singleton::GetInstance() {
      static once_flag flag;
      call_once(flag, [&]() {
        instance_.reset(new Singleton());
      });
      return *instance_;
    }
    

其他问题

参考

  1. C++11 使用智能指针改进单例模式_HelloKandy的博客-CSDN博客
  2. 设计模式 - 单例模式 - 《C++那些事(Stories About C Plus Plus)》 - 书栈网 · BookStack
  3. C++ 线程安全的单例模式总结 - 小林coding - 博客园 (cnblogs.com)