添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《 阿里云开发者社区用户服务协议 》和 《 阿里云开发者社区知识产权保护指引 》。如果您发现本社区中有涉嫌抄袭的内容,填写 侵权投诉表单 进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。 static Singleton* getInstance() { if(nullptr == _pInstance) { _pInstance = new Singleton(); return _pInstance; private: // 构造函数和析构函数私有化 Singleton(); ~Singleton(); // 禁掉拷贝构造、拷贝赋值、移动构造、移动赋值 Singleton(const Singleton &) = delete; Singleton& operator=(const Singleton&) = delete; Singleton(Singleton &&) = delete; Singleton& operator=(Singleton &&) = delete; private: // 静态成员:静态成员函数只能访问静态成员 static Singleton* _pInstance; // 静态成员需要初始化 Singleton* Singleton::_instance = nullptr; // 线程安全,双重检测:double check if (nullptr == _pInstance) { std::lock_guard<std::mutex> lock(_mutex); if (nullptr == _pInstance) { // 问题:多线程环境下,cpu reorder _pInstance = new Singleton(); // 注册回调函数,进程结束后,调用销毁函数 atexit(Singleton::Destructor); return _pInstance; private: Singleton(); ~Singleton(); Singleton(const Singleton &) = delete; Singleton& operator=(const Singleton&) = delete; Singleton(Singleton &&) = delete; Singleton& operator=(Singleton &&) = delete; // 注册销毁函数为atexit的回调函数,用于在进程结束后释放堆空间 static void Destructor() { if (nullptr != _instance) { delete _instance; _instance = nullptr; private: static Singleton* _pInstance; Singleton* Singleton::_instance = nullptr;

问题:new 操作符指令重排

C++ 98 表达单线程语义。而在多核多线程的情况下,若 cpu 指令重排,例如:对于 new 运算符的指令执行:分配内存、调用构造函数、返回指针。若发生 cpu 指令重排,会优化为分配内存、返回指针,却还没有调用构造函数初始化数据。此时,若有其他线程访问,可能造成程序的崩溃。

实现 3:原子变量 + 懒汉模式

C++ 11:多线程语义,cpu 指令重排,提供同步原语:原子变量、内存屏障等

原子变量解决

  • 原子性问题
  • 可见性问题:load 可以看见其他线程最新操作的数据, store 修改数据让其他线程可见
  • 执行序问题: memory_order_acuire 不能重排指令, memory_order_release 松散指令,可以重排指令。
  • 内存屏障(内存栅栏)解决

  • 可见性问题
  • 执行序问题
  • 使用原子变量解决原子性、可见性、执行序

    public: static Singleton * GetInstance() { Singleton* tmp = _instance.load(std::memory_order_acquire); if (tmp == nullptr) { std::lock_guard<std::mutex> lock(_mutex); tmp = _instance.load(std::memory_order_acquire); if (tmp == nullptr) { tmp = new Singleton; _instance.store(tmp, memory_order_release); atexit(Destructor); return tmp; static std::atomic<Singleton*> _instance; static std::mutex _mutex; std::atomic<Singleton*> Singleton::_instance; // 静态成员需要初始化 std::mutex Singleton::_mutex; // 互斥锁初始化

    改进:若构造函数中存在其他原子性操作,则可以使用松散的指令执行方式,提升运行速度。使用内存屏障,避免 tmp 指针在 new 操作未执行完就返回给用户。

  • 原子变量解决:原子性、可见性
  • 内存栅栏解决:执行序
  • public: static Singleton * GetInstance() { Singleton* tmp = _instance.load(std::memory_order_relaxed); // 获取内存屏障 std::atomic_thread_fence(std::memory_order_acquire); if (tmp == nullptr) { std::lock_guard<std::mutex> lock(_mutex); tmp = _instance.load(std::memory_order_relaxed); if (tmp == nullptr) { tmp = new Singleton; // 释放内存屏障 std::atomic_thread_fence(std::memory_order_release); _instance.store(tmp, std::memory_order_relaxed); atexit(Destructor); return tmp; static std::atomic<Singleton*> _instance; static std::mutex _mutex; std::atomic<Singleton*> Singleton::_instance; // 静态成员需要初始化 std::mutex Singleton::_mutex; // 互斥锁初始化 static Singleton* Singleton::getInstance() { if(nullptr == _pInstance) { _pInstance = new Singleton(); atexit(Singleton::Destructor); return _pInstance; // 全局初始化,使其在进程创建之前就不为空,防止子进程创建对象 Singleton* Singleton::_instance = getInstance();

    问题:无论是否需要该类实例,都必须提前创建。

    * 实现5:magic static

    源自:C++ effective,C++ 11 magic static 特性,参考官方文档: 静态局部变量 static / thread local ,推荐使用。

  • 静态局部变量在初始化的时候,并发线程同时进入声明语句,并发线程会阻塞等待其初始化结束。线程安全。
  • 静态局部变量首次经过它的声明才会被初始化,在其后所有的调用中,声明都会被跳过。
  • 因此,使用定义在栈上的局部静态变量保存单例对象,具备所有优点:

  • 系统自动调用析构函数,回收内存
  • 没有 new 操作带来的 cpu reorder 操作
  • ~Singleton(); Singleton(const Singleton &) = delete; Singleton& operator=(const Singleton&) = delete; Singleton(Singleton &&) = delete; Singleton& operator=(Singleton &&) = delete;