单例模式-C++实现

目录

    • 饿汉式
    • 懒汉式
    • 双检查锁,线程安全的版本
    • 什么是reorder?
    • 解决内存读写reorder不安全方法
    • 代码解释
    • 懒汉式的优缺点

单例模式是一种设计模式,用于确保一个类只有一个实例,并提供一个全局的访问点来获取该实例。它常用于需要在整个应用程序中共享相同资源或状态的情况下。

单例模式分为饿汉式懒汉式

饿汉式

在饿汉式中,实例在类加载时就被初始化,并且保证在多线程环境下的线程安全。

// 饿汉式
class Singleton {
private:static Singleton* instance; // 静态成员变量,保存实例指针Singleton() {} // 构造函数私有化Singleton(const Singleton& other) {}	// 拷贝构造函数私有化Singleton& operator=(const Singleton&) {} // 赋值运算符私有化public:static Singleton* getInstance() {return instance;}
};Singleton* Singleton::instance = new Singleton(); // 在类加载时初始化实例// 使用示例
Singleton* obj1 = Singleton::getInstance();
Singleton* obj2 = Singleton::getInstance();// obj1 和 obj2 是同一个实例

饿汉式下类的实例对象在类加载时就被创建并赋值给静态成员变量instance,因此不需要考虑线程安全问题。因为每次调用getInstance方法都会返回同一个实例。

饿汉式的优点是实现简单,线程安全;缺点是无法实现延迟加载,即类在加载时就创建好了实例,可能会浪费资源。

懒汉式

懒汉式的实现方法是将实例的创建延迟到第一次请求访问时再进行初始化,这样可以避免初始化时资源的浪费和额外的开销,但需要考虑多线程之间的线程安全问题。

class Singleton
{
private:Singleton() {}Singleton(const Singleton& other) {}Singleton& operator=(const Singleton&) {} // 赋值运算符私有化
public:static Singleton* getInstance();static Singleton* Singleton::m_instance;
};Singleton* Singleton::m_instance = nullptr;// 线程不安全
Singleton* Singleton::getInstance()
{if (m_instance == nullptr)m_instance = new Singleton();return m_instance;
}

在getInstance方法中,我们先判断m_instance是否为空,为空就new一个出来。但是这样在单线程下是安全的,因为m_instance只会被创建一次,在多线程下可能会被创建多次。

双检查锁,线程安全的版本

class Singleton
{
private:Singleton() {}Singleton(const Singleton& other) {}Singleton& operator=(const Singleton&) {} // 赋值运算符私有化public:static Singleton* getInstance();static Singleton* m_instance;static std::mutex m_mutex;
};Singleton* Singleton::m_instance = nullptr;
std::mutex m_mutex;// 多线程安全,但锁的代价过高
Singleton* Singleton::getInstance()
{std::lock_guard<std::mutex> lock(m_mutex);if (m_instance == nullptr)m_instance = new Singleton();return m_instance;
}

这个版本在多线程下是安全的,因为加锁了,但是在读操作的情况下,也就是如果m_instance直接返回时是不需要加锁的,所以这个版本在高并发的情况下开销很大,很耗时,因为不管是写操作还是读操作都需要加锁减锁。

为了解决这个问题,我们可以使用双检查锁来避免这样的开销问题

// 双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance()
{if (m_instance == nullptr){std::lock_guard<std::mutex> lock(m_mutex);if (m_instance == nullptr)m_instance = new Singleton();}return m_instance;
}

锁前检查是否为空是为了避免读操作下还进行加锁,锁后检查是为了避免多次创建。

但是这样还是有问题,内存读写reorder不安全。

什么是reorder?

reorder就是在编译器底层进行优化重排指令的执行顺序。

举个例子:
m_instance = new Singleton();
这行代码在编译器底层大致可以分为三个步骤:
1、分配内存
2、调用构造器对内存进行初始化
3、将内存的地址赋值给m_instance

但在实际的运行过程中,编译器执行的顺序可能是1-》3-》2,这就会导致当多个线程同时调用getInstance方法并且m_instance为nullptr时,它们可能会同时通过if语句的判断条件进入临界区。在这种情况下,第一个线程通过了if语句的条件检查,并在锁内部实例化了Singleton对象。但由于内存读写重排序的存在,编译器或处理器可能会将Singleton对象的初始化操作重排到锁的外部,这会导致第二个线程在第一个线程完成实例化之前通过了if语句的条件检查,直接返回使用,但此时m_instance还没有进行初始化。

解决内存读写reorder不安全方法

为了解决这个问题,我们可以使用如下代码实现,支持C11以上版本并跨平台

class Singleton
{
private:Singleton();Singleton(const Singleton& other);public:static Singleton* getInstance();static std::atomic<Singleton*> m_instance;static std::mutex m_mutex;
};std::atomic<Singleton*> Singleton::m_instance;
std::mutex m_mutex;Singleton* Singleton::getInstance()
{Singleton* tmp = m_instance.load(std::memory_order_relaxed);std::atomic_thread_fence(std::memory_order_acquire);if (tmp == nullptr){std::lock_guard<std::mutex> lock(m_mutex);tmp = m_instance.load(std::memory_order_relaxed);if (tmp == nullptr){tmp = new Singleton;std::atomic_thread_fence(std::memory_order_release);m_instance.store(tmp, std::memory_order_relaxed);}}return tmp;
}

代码解释

首先,使用了双重检查锁定来提高性能。代码开始时,通过调用 m_instance.load(std::memory_order_relaxed) 加载 m_instance 变量的值,并将结果赋给 tmp 变量。

接下来,通过调用 std::atomic_thread_fence(std::memory_order_acquire) 来添加内存屏障,保证之前的读操作完成后,之后的读写操作不会被重排序。

然后,通过判断 tmp 是否为 nullptr,来确定是否需要创建实例。如果 tmp 是 nullptr,表示还没有创建实例,需要进行创建。

在创建实例之前,先获取一个互斥锁 m_mutex,确保只有一个线程可以访问临界区代码。

再次检查 tmp 是否为 nullptr,是为了防止多个线程同时通过第一个检查而进入临界区,因为在第一个检查后可能已经有其他线程创建了实例。

在临界区内部,首先创造了一个 Singleton 类的实例 tmp。然后通过 std::atomic_thread_fence(std::memory_order_release) 添加内存屏障,确保在 tmp 赋值完成后,该实例的构造函数中的其他写操作不会被重排序。

最后,通过调用 m_instance.store(tmp, std::memory_order_relaxed) 将 tmp 存储到 m_instance 变量中。

在临界区外部,返回已经创建的实例 tmp。

这种实现方式既兼顾了性能又保证了线程安全。通过使用双重检查锁定和互斥锁,可以避免多个线程同时创建实例,同时使用原子操作和内存屏障来保证实例的可见性和有序性。

懒汉式的优缺点

优点:
1、延迟加载:懒汉式在需要用到实例的时候才创建,可以在程序启动时减少不必要的消耗。
2、节约内存:懒汉式只会在用到对象时创建,避免了无谓的内存占用。

缺点:
1、线程安全性问题:多线程下同时获取实例时,可能会造成实例被多次创建的问题
2、性能问题:在多线程环境下,为了保证实例被唯一创建,需要引入额外的同步开销,高并发下会影响性能
3、实现复杂:为了保证线程安全,需要使用双检查锁等方法增加了代码的复杂性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/175214.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【软考】模块的内聚类型

目录 一、偶然内聚二、逻辑内聚三、时间内聚四、过程内聚五、通信内聚六、顺序内聚七、功能内聚 一、偶然内聚 1.最弱的内聚类型 2.又称巧合内聚&#xff0c;模块的各个成分之间毫无关系 二、逻辑内聚 1.逻辑上相关的功能被放在同一个模块中 2.如一个模块读取各种不同类型外设…

QTextEdit 是 Qt 框架中的一个类,用于显示和编辑多行文本内容的可编辑部件

QTextEdit 是 Qt 框架中的一个类&#xff0c;用于显示和编辑多行文本内容的可编辑部件。 QTextEdit 提供了一个用于显示和编辑富文本&#xff08;包括格式化文本、图像和链接等&#xff09;和纯文本的文本编辑器。它支持基本的文本操作&#xff08;如复制、粘贴、撤销、重做等…

自己动手实现一个深度学习算法——八、深度学习

深度学习是加深了层的深度神经网络。 1.加深网络 1&#xff09;向更深的网络出发 创建一个如下图所示的网络结构的CNN 这个网络的层比之前实现的网络都更深。这里使用的卷积层全都是33 的小型滤波器&#xff0c;特点是随着层的加深&#xff0c;通道数变大&#xff08;卷积…

FLASK博客系列7——我要插入数据库

我们来继续上次的内容&#xff0c;实现将数据插入数据库。 我们先更改下models.py&#xff0c;由于上次笔误&#xff0c;把外键关联写错了。在这里给大家说声抱歉。不过竟然没有小伙伴发现。 models.py from app import dbclass User(db.Model): # 表名将会是 user&#xff0…

针对无法创建JDK8版本的SpringBoot项目

自从昨日起&#xff0c;JDK突然改版了&#xff0c;不能在使用JDK8来进行创建Spring Boot项目了&#xff0c;导致目前小编的JDK无从下手~~ 因此&#xff0c;在仔细琢磨了一晚之后&#xff0c;想起来了以下几个方法&#xff0c;与大家共勉。希望能够得到大家的认可&#xff01; …

生命科学领域 - 新药从研发到上市全流程

新药是指新研制的、临床尚未应用的药物&#xff0c;其化学本质应为新的化合物或称新化学实体、 新 分子实体、新活性实体。新药研发的根本目的是治疗疑难危重疾病&#xff0c;研制出来的药物即使是全新的化学结构&#xff0c;但是疗效或安全性却不及现有的药物便失去新药价值&a…

Redis面试题:哨兵模式相关问题,以及脑裂问题

目录 面试官&#xff1a;怎么保证Redis的高并发高可用 面试官&#xff1a;你们使用redis是单点还是集群&#xff0c;哪种集群 面试官&#xff1a;redis集群脑裂&#xff0c;该怎么解决呢&#xff1f; 面试官&#xff1a;怎么保证Redis的高并发高可用 候选人&#xff1a;首先…

人工智能-优化算法之凸集

凸性 凸性&#xff08;convexity&#xff09;在优化算法的设计中起到至关重要的作用&#xff0c; 这主要是由于在这种情况下对算法进行分析和测试要容易。 换言之&#xff0c;如果算法在凸性条件设定下的效果很差&#xff0c; 那通常我们很难在其他条件下看到好的结果。 此外&…

统信UOS安装Virtualbox虚拟机和Windows10系统

在UOS统信系统中部署Windows环境我可以通过安装虚拟机来实现&#xff0c;这也可以解决软件不适配带来的一些问题&#xff0c;当然对硬件配置也有一定的要求&#xff0c;不建议性能过低的设备使用。 接下来请按照以下步骤进行安装Virtualbox及Win10虚拟系统的设置。 1、安装Vi…

数字滚动vue-count-to

数字滚动 下载插件 npm i vue-count-to 使用 start-val 起始值&#xff0c;表示从什么值开始滚 end-val 终点值&#xff0c;表示要滚到多大值 duration 滚动事件&#xff0c;表示用多长时间来滚动 <countTo :start-val"0" :end-val"228" :duration&quo…

ETL+BI结合的数据集成工具

在当今信息化时代&#xff0c;企业积累了大量的数据资产&#xff0c;如何高效地提取、转换和加载&#xff08;ETL&#xff09;这些数据&#xff0c;并将其转化为有用的洞察力成为了企业取得竞争优势的关键。同时&#xff0c;商业智能&#xff08;BI&#xff09;作为一种数据驱动…

微软重磅更新:Bing Chat全线改名Copilot,用户可免费使用GPT4!(文末附Copilot使用教程)

原创 | 文 BFT机器人 微软在2023年的Ignite大会上宣布了许多新产品和功能。其中最引人注目的是Bing Chat更名为Copilot&#xff0c;Copilot基于最新的OpenAI模型&#xff0c;包括GPT-4和DALL・E 3&#xff0c;为用户提供文本和图像生成功能。也就是说&#xff0c;只要你拥有微…

Flutter 桌面应用开发之读写Windows注册表

文章目录 需求来源Windows查询Windows版本号方法1. 如何查看Windows版本号2. Windows开发如何通过代码查询Windows版本号(1) 使用C#代码&#xff1a;(2) 使用VB.NET代码 3.通过注册表查看Windows版本信息 Flutter查询Windows版本号方法依赖库支持平台实现步骤1. 在pubspec.yaml…

记一次mysql 锁表 的经历 : Lock wait timeout exceeded; try restarting transaction

场景&#xff1a;线程a 进行insert 操作&#xff0c;线程b 进行update 操作 。a 和b 都添加了事务。 报错&#xff1a;线程a insert 的时候 报锁等待超时 处理方式&#xff1a; 1。#查询是否锁表 show OPEN TABLES where In_use > 0; 2。#查询表被锁进程&#xff1b;查询到…

opencv-医学图像预处理

医学图像预处理通常需要针对特定任务和数据集的特点进行定制。以下是一些常见的医学图像预处理步骤&#xff0c;可以使用OpenCV以及其他相关库来实现&#xff1a; 导入相关的库 import cv2 import matplotlib.pyplot as plt1. 读取图像 image cv2.imread(r"C:\Users\m…

【图像分类】基于深度学习的中草药分类系统的设计与实现(ResNet网络,附代码和数据集)

写在前面: 首先感谢兄弟们的关注和订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。(专栏订阅用户订阅专栏后免费提供数据集和源码一份,超级VIP用户不在服务范围之内,不想订阅专栏的兄弟们可以私信…

从0开始学习JavaScript--JavaScript对象继承深度解析

JavaScript中的对象继承是构建灵活、可维护代码的关键部分。本文将深入讨论JavaScript中不同的继承方式&#xff0c;包括原型链继承、构造函数继承、组合继承等&#xff0c;并通过丰富的示例代码展示它们的应用和差异。通过详细解释&#xff0c;大家可以更全面地了解如何在Java…

nodejs+vue+elementui+express青少年编程课程在线考试系统

针对传统线下考试存在的老师阅卷工作量较大&#xff0c;统计成绩数据时间长等问题&#xff0c;实现一套高效、灵活、功能强大的管理系统是非常必要的。该系统可以迅速完成随机组卷&#xff0c;及时阅卷、统计考试成绩排名的效果。该考试系统要求&#xff1a;该系统将采用B/S结构…

【JMeter】使用BeanShell写入内容到文件

一、前言 在我们日常工作中&#xff0c;可能会遇到需要将请求返回的数据写入到文件中。在我们使用JMeter进行性能测试时&#xff0c;就经常能够遇到这种情况。要想达到这种目的&#xff0c;我们一般采取BeanShell后置处理器来将内容写入到文件。 二、提取 在目前大多数的性能…

11.22数电第四次报告

《数字逻辑》实验报告 姓名 贾轲 年级 22 学号 20220669 专业、班级 计算机科学与技术计卓01 实验名称 实验十五 摩尔状态机序列检测器&实验十六 米利状态机序列检测器 实验时间 2023.11.23 实验地点 DS1410 实验成绩 实验性质 □验证性 □设计性 □…