当前位置: 首页 > news >正文

单例模式-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、实现复杂:为了保证线程安全,需要使用双检查锁等方法增加了代码的复杂性。

相关文章:

单例模式-C++实现

目录 饿汉式懒汉式双检查锁&#xff0c;线程安全的版本什么是reorder&#xff1f;解决内存读写reorder不安全方法代码解释懒汉式的优缺点 单例模式是一种设计模式&#xff0c;用于确保一个类只有一个实例&#xff0c;并提供一个全局的访问点来获取该实例。它常用于需要在整个应…...

一种模板类实现和声明分开在生成的.a文件被使用时出现undefined reference时的一种解决方法

一种模板类实现和声明分开在生成的.a文件被使用时出现undefined reference时的一种解决方法 模板类头文件格式如下&#xff1a; test.h // test.h namespace test { namespace _testspace { class base { public: base(); ~base(); };template<bool T> class base_impl…...

js用到的算法

1.对象数组中&#xff0c;对象中有对象&#xff0c;数组根据对象中的对象打平 [{indexValueMap: { 68443: 0, 68457: 0 },rowName1: 固定收益类,rowName2: 交易类,rowName3: 次级},{indexValueMap: { 68443: 0, 68457: 0 },rowName1: 固定收益类,rowName2: 交易类,rowName3: 中…...

【科技素养】蓝桥杯STEMA 科技素养组模拟练习试卷9

1、商标也属于知识产权的一种。一个商标在注册之后&#xff0c;将会在&#xff08;&#xff09;的时间受到保护 A、20 年内 B、50 年内 C、直至注册人去世 D、10 年内 答案&#xff1a;D 2、人类史上第一位进入太空的宇航员是&#xff08;&#xff09;&#xff0c;他/她是…...

如何使用抖音直播调试入口扫码进行调试

使用抖音直播调试入口扫码进行调试的步骤如下&#xff1a; 确保你已经安装了抖音调试助手。打开调试助手&#xff0c;并在主界面点击“连接”按钮。在连接向导页面&#xff0c;根据提示连接你的抖音直播间。请确保你已经获取了直播间的token和scheme。连接成功后&#xff0c;你…...

AI智能人机对话小程序系统源码 附带完整的搭建教程

移动互联网的普及和快速发展&#xff0c;小程序已经成为了一种非常流行的应用形态。小程序具有即用即走、轻量级的特点&#xff0c;非常适合用于提供各种便捷服务。下面罗峰来给大家分享一款AI智能人机对话小程序系统源码&#xff0c;带有完整的搭建教程。 以下是部分代码示例…...

【腾讯云云上实验室】用向量数据库在金融信数据库分析中的实战运用

一、前言 这篇文章将带领读者探索数据库的多样化解决方案及其演进历程&#xff0c;特别关注向量数据库的重要性和在实际项目中的应用。 通过深入剖析腾讯云向量数据库及其在金融信用数据库分析中的实战运用&#xff0c;为读者提供全面而实用的指南&#xff0c;帮助他们理解、…...

2015年五一杯数学建模A题不确定性条件下的最优路径问题解题全过程文档及程序

2015年五一杯数学建模 A题 不确定性条件下的最优路径问题 原题再现 目前&#xff0c;交通拥挤和事故正越来越严重的困扰着城市交通。随着我国交通运输事业的迅速发展&#xff0c;交通“拥塞”已经成为很多城市的“痼疾”。在复杂的交通环境下&#xff0c;如何寻找一条可靠、快…...

5、Qt:项目中包含多个子项目(.pro)/子模块(.pri)

一、说明&#xff1a; 在进行项目开发过程中&#xff0c;会涉及子项目/子模块的问题 Qt中使用TEMPLATE subdirs添加多个子项目&#xff1b;子项目可以单独编译生成可执行文件&#xff08;exe&#xff09;或者动态链接库&#xff08;dll&#xff09;等&#xff0c;供其他模块…...

Facebook的特点优势

Facebook作为全球最大的社交媒体平台之一&#xff0c;同时也是最受欢迎的社交网站之一&#xff0c;Facebook具有许多独特的特点和优势。本文小编将说一些关于Facebook的特点及优势。 1、全球化 Facebook拥有数十亿的全球用户&#xff0c;覆盖了几乎所有国家和地区。这使得人们…...

Spring框架体系及Spring IOC思想

目录 Spring简介Spring体系结构SpringIOC控制反转思想自定义对象容器Spring实现IOCSpring容器类型容器接口容器实现类对象的创建方式使用构造方法使用工厂类的方法使用工厂类的静态方法对象的创建策略对象的销毁时机生命周期方法获取Bean对象的方式通过id/name获取通过类型获取…...

WT588F02B-8S语音芯片:16位DSP技术引领个性化功能产品新时代

随着科技的快速发展&#xff0c;语音芯片作为人机交互的核心组件&#xff0c;在各个领域的应用越来越广泛。唯创知音推出的WT588F02B-8S语音芯片&#xff0c;以其强大的16位DSP技术和丰富的内置资源&#xff0c;正成为行业内的翘楚。 首先&#xff0c;唯创知音WT588F02B-8S是一…...

数字逻辑电路基础-时序逻辑电路之移位寄存器

文章目录 一、移位寄存器定义二、verilog源码三、仿真结果 一、移位寄存器定义 移位寄存器定义 A shift register is a type of digital circuit using a cascade of flip flops where the output of one flip-flop is connected to the input of the next. 移位寄存器是一种将…...

DEM分析

一、实验名称&#xff1a; DEM分析 二、实验目的&#xff1a; 通过本实验练习&#xff0c;掌握DEM的建立与应用基本方法。 三、实验内容和要求&#xff1a; 实验内容&#xff1a; 利用ARCGIS软件相关分析工具及实验数据&#xff0c;创建DEM&#xff0c;并计算相应坡度的区…...

全面探讨HTTP协议从0.9到3.0版本的发展和特点

前言&#xff1a; 最近的几场面试都问到了http的相关知识点&#xff0c;博主在此结合书籍和网上资料做下总结。本篇文章讲收录到秋招专题&#xff0c;该专栏比较适合刚入坑Java的小白以及准备秋招的大佬阅读。 如果文章有什么需要改进的地方欢迎大佬提出&#xff0c;对大佬有帮…...

中通快递查询入口,根据物流更新量筛选出需要的单号记录

批量中通快递单号的物流信息&#xff0c;根据物流更新量将需要的单号记录筛选出来。 所需工具&#xff1a; 一个【快递批量查询高手】软件 中通快递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;并登录 步骤2&#xff1a;点击主…...

Arraylist案例

Arraylist是使用最频繁的一个集合&#xff0c;它与数组类似&#xff0c;不同之处在于它可以动态改变长度&#xff0c;不够了可以扩容。 案例&#xff1a; 我的思考&#xff1a; 首先多个菜品信息可以用Arraylist 来存储&#xff0c;那我们需要再创建一个菜品类Food&#xff0…...

『heqingchun-Ubuntu系统+x86架构+配置编译安装使用yolov5-6.0+带有TensorRT硬件加速+C++部署』

Ubuntu系统x86架构配置编译安装使用yolov5-6.0带有TensorRT硬件加速C部署 一、准备文件 1.yolov5-6.0.zip 官网下载 网址&#xff1a; https://github.com/ultralytics/yolov5/tree/v6.0操作&#xff1a; 点击"Code"下的"Download ZIP" 下载得到yolov5…...

优秀的员工成为公司的管理者之后,为何表现平庸?因为他们缺乏这些思维

在企业的实践中&#xff0c;我们发现平时能力最强的员工&#xff0c;在被提拔到管理层之后就慢慢变得平庸了&#xff0c;再也不是以前那个无所不能的“企业能人”了&#xff0c;甚至在一些事情的处理上还会有些笨拙。面对这种情况&#xff0c;我们一定会感觉很疑惑&#xff0c;…...

MySQL简单介绍

简单了解MySQL MySQL语句分类 SQL语句分类 DDL&#xff1a;数据定义语句 create表&#xff0c;库.….] DML&#xff1a;数据操作语句 [增加insert&#xff0c;修改 update&#xff0c;删除delete] DQL&#xff1a;数据查询语句 [select] DCL&#xff1a;数据控制语句 …...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

如何在看板中有效管理突发紧急任务

在看板中有效管理突发紧急任务需要&#xff1a;设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP&#xff08;Work-in-Progress&#xff09;弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中&#xff0c;设立专门的紧急任务通道尤为重要&#xff0c;这能…...

将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?

Otsu 是一种自动阈值化方法&#xff0c;用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理&#xff0c;能够自动确定一个阈值&#xff0c;将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

&#x1f680; C extern 关键字深度解析&#xff1a;跨文件编程的终极指南 &#x1f4c5; 更新时间&#xff1a;2025年6月5日 &#x1f3f7;️ 标签&#xff1a;C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言&#x1f525;一、extern 是什么&#xff1f;&…...

OpenLayers 分屏对比(地图联动)

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能&#xff0c;和卷帘图层不一样的是&#xff0c;分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)

船舶制造装配管理现状&#xff1a;装配工作依赖人工经验&#xff0c;装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书&#xff0c;但在实际执行中&#xff0c;工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险

C#入门系列【类的基本概念】&#xff1a;开启编程世界的奇妙冒险 嘿&#xff0c;各位编程小白探险家&#xff01;欢迎来到 C# 的奇幻大陆&#xff01;今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类&#xff01;别害怕&#xff0c;跟着我&#xff0c;保准让你轻松搞…...

C# 表达式和运算符(求值顺序)

求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如&#xff0c;已知表达式3*52&#xff0c;依照子表达式的求值顺序&#xff0c;有两种可能的结果&#xff0c;如图9-3所示。 如果乘法先执行&#xff0c;结果是17。如果5…...

tomcat指定使用的jdk版本

说明 有时候需要对tomcat配置指定的jdk版本号&#xff0c;此时&#xff0c;我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...