• 单例模式4种实现方式C++


    单例模式:保证一个类只有一个实例,并提供一个该实例的全局访问点

    实现方式:构造和拷贝构造设为私有,总共介绍四个版本,推荐使用最后一个。版本1、2、3、4全都是懒汉式的写法。

    版本1:线程非安全版本

    在多线程情况下可能会同时创建出多个对象,比如,现在有线程A和线程B,线程A进入到16行时, 线程B进入17行,会容易new出多个实例。

    版本2:线程安全,但锁的代价过高

    在GetInstance()中使用局部变量锁, 保证同一时刻只有一个线程访问30-33行。
    读变量没必要加锁,尤其是在高并发的情况下,代价还是挺高的。

    版本3:双检查锁,但由于内存读写reorder(重新排序)不安全(导致双检查锁的失效)

    锁前检查,避免都是读取操作时锁代价过高的问题
    锁后检查,避免两个线程同时进入,从而new了两个实例

    因为编译器优化,指令的执行顺序可能reorder(CPU执行指令的层次,而且线程是在指令层次抢时间片的) ,可能变成这样:分配内存->赋值->调用构造(理想应该是:分配内存->调用构造->赋值)
    假设在reorder的情况下:线程1走到赋值,但还没调用构造的阶段,而线程2进来判断m_instance,此时它已经被复制 所以不为空,这时候线程2就直接返回m_instance,但事实上它还没构造出来……这就尴尬了,实际上因为它没有构造,肯定是不能用的。
    总之,就是双检查锁 它欺骗了线程2……

    怎么解决这个问题呢?
    Java 和c sharp 添加了一个关键字:volatile
    这样,编译时在编译的时候就知道,这个变量的整个赋值过程不能reorder,需要按照常规的流程走。

    C++11之后 跨平台实现了volatile
    还是挺复杂的哈……
    在这里插入图片描述

    前3个版本均来自侯捷老师的视频,可以去观看一下,讲的灰常简单易懂

    版本4:局部静态变量实现单例模式,线程安全(推荐使用)
    class Singleton{
    public:
    	~Singleton();
    	static Singleton& getInstance()
    	{
    		static Singleton instance;
    		return instance;	
    	}
    private:
    	Singleton();
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    原因是C++ 11标准中新增了一个特性叫Magic Static:如果变量在初始化时,并发线程同时进入到static声明语句,并发线程会阻塞等待初始化结束。
    这样可以保证在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性,同时也避免了new对象时指令重排序造成对象初始化不完全的现象。并且相比较与使用智能指针以及mutex来保证线程安全和内存安全来说,这样做能够提升效率。

  • 相关阅读:
    GAN入门|第二篇:人脸图像生成(DCGAN)
    代码复现: VoxelNet论文和代码解析 pytorch版本(二) Dataloader.py
    网络系统结构与设计的基本原则
    markdown转ipynb--利用包notedown
    数据结构之单向链表
    在断更的日子里,我想了很多... ...
    消息队列|RabbitMQ入门概述
    《Real-Time Rendering 3rd》读书笔记
    Springboot之事件管家SpringApplicationRunListeners事件管理机制源码分析
    视频分段方法:视频批量处理与音频提取的操作解析
  • 原文地址:https://blog.csdn.net/qq_41363459/article/details/125473667