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

C++设计模式:单例模式(十)

1、单例设计模式
  • 单例设计模式,使用的频率比较高,整个项目中某个特殊的类对象只能创建一个

  • 并且该类只对外暴露一个public方法用来获得这个对象。

  • 单例设计模式又分懒汉式和饿汉式,同时对于懒汉式在多线程并发的情况下存在线程安全问题

    • 饿汉式:类加载的准备阶段就会将static变量、代码块进行实例化,最后只暴露一个public方法获得实例对象。

    • 懒汉式:当需要用到的时候再去加载这个对象。这时多线程的情况下可能存在线程安全问题

  • 对于饿汉式这里不做具体的解释,本节只讨论多线程与懒汉式的线程安全问题

2、单线程下的懒汉模式
2.1、单例对象的创建:
  • 将类指针对象进行静态私有化,并且在类外初始化这个对象为空;静态能保证的是这个对象属于这个类不属于任何一个对象

  • 私有化空构造器防止可以实例化对象

  • 对外暴露一个public方法获取该对象,如果在获取时发现该对象为空,那么进行实例化,否则直接返回

  • 因此可以看到实例化只有一次,多次获取到的对象的地址属于同一个

  • 可以通过内部类的方式进行析构

    • 首先在单例类内部进行私有化一个内部类
    • 对外暴露的public获取instance的对象接口在new实例化对象的时候创建一个内部类静态成员
    • 内部类静态成员的好处是只有一份
    • 当作用域结束时内部类就会负责析构掉主类的静态成员对象
class Single_Instance {
private:static Single_Instance *instance;Single_Instance() {}Single_Instance(const Single_Instance & s){}class inner_class {public:~inner_class(){if(Single_Instance::instance){delete Single_Instance::instance;Single_Instance::instance = NULL;std::cout << "inner_class::~inner_class(), 析构Single_Instance::instance对象" << std::endl;}}};
public:static Single_Instance *get_Instance(){if(instance == NULL){instance = new Single_Instance();static inner_class innerClass;}return instance;}void func(){std::cout << "func(), &instance = " << instance << std::endl;}
};
Single_Instance *Single_Instance::instance = NULL;
3、单例模式与多线程
  • 单例模式的对象可能会被多个线程使用,但是又必须保证这个单例的对象只有一份

  • 不能重复创建、也必须保证这个对象在多线程使用过程中不会因为创建而产生数据安全问题,即多线程抢占的创建这一个对象

class Single_Instance {
private:static Single_Instance *instance;Single_Instance() {}Single_Instance(const Single_Instance & s){}class inner_class {public:~inner_class(){if(Single_Instance::instance){delete Single_Instance::instance;Single_Instance::instance = NULL;std::cout << "inner_class::~inner_class(), 析构Single_Instance::instance对象" << std::endl;}}};
public:static Single_Instance *get_Instance(){if(instance == NULL){instance = new Single_Instance();static inner_class innerClass;}return instance;}void func(){std::cout << "func(), &instance = " << instance << std::endl;}
};
Single_Instance *Single_Instance::instance = NULL;void thread_func()
{std::cout << "子线程开始执行了" << std::endl;Single_Instance *instance = Single_Instance::get_Instance();std::cout << "thread_func, &instance = " << instance << std::endl;std::cout << "子线程执行结束了" << std::endl;
}void test2()
{std::thread mythread1(thread_func);std::thread mythread2(thread_func);std::thread mythread3(thread_func);std::thread mythread4(thread_func);mythread1.join();mythread2.join();mythread3.join();mythread4.join();
}

在这里插入图片描述

可以看到实例化不止一个单例对象,这一现象违反了单例的思想,因此需要在多线程抢占创建时进行互斥(mutex)

3.1、解决方案(一)
  • 使用互斥量的方式,对线程访问获取对象进行阻塞
  • 但是不难发现问题,其实这个对象只创建一次,之后的访问单纯的获取这个对象也要进行加锁逐个排队访问临界区,这一现象导致效率极低
std::mutex mutex_lock;
static Single_Instance *get_Instance(){std::unique_lock<std::mutex> uniqueLock(mutex_lock);if(instance == NULL){instance = new Single_Instance();static inner_class innerClass;}return instance;
}
3.2、解决方式(二)

双重检查机制(DCL)进行绝对安全解决

  • 双重检查:
    • 首先在锁外面加入一个if判断,判断这个对象是否存在,如果存在就没有必要上锁创建,直接返回即可
    • 如果对象不存在,首选进行加锁,然后在if判断对象是否存在,这个if的意义在于当多个线程阻塞在mutex锁头上时
    • 突然有一个线程1创建好了,那么阻塞在mutex锁头上的线程2、3、4…都不用再继续创建,因此在加一个if判断

这里还需要解释一下volatile关键字:

  • volatile关键字的作用是防止cpu指令重排序,重排序的意思就是干一件事123的顺序,cpu可能重排序为132

  • 为什么需要防止指令重排序,因为对象的new过程分为三部曲:

    (1)分配内存空间、(2)执行构造方法初始化对象、(3)将这个对象指向这个空间;

    由于程序运行CPU会进行指令的重排序,如果执行的指令是132顺序,A线程执行完13之后并没有完成对象的初始化、而这时候转到B线程;B线程认为对象已经实例化完毕、其实对象并没有完成初始化!产生错误

static Single_Instance *get_Instance(){if(instance == NULL){std::unique_lock<std::mutex> uniqueLock(mutex_lock);if(instance == NULL){instance = new Single_Instance();static inner_class innerClass;}}return instance;
}
3.3、解决方案(三)
  • 但volatile关键字并不跨平台,而在C++11中提供了一种新的标准来解决这一问题,并且跨平台
  • 可以通过atomic原子类来保证
    • 将对象声明为原子类的指针
    • std::atomic_thread_fence(std::memory_order_acquire):获取内存屏蔽的屏障,关闭reorder
    • std::atomic_thread_fence(std::memory_order_release):将instance对象创建完毕之后进行解开内存屏障
    • instance.store(tmp, std::memory_order_relaxed):将对象store到内存中。
  • Atomic类主要通过CAS锁来实现的,具体点击这里
class Single_Instance {
private:std::atomic<Single_Instance*> instance;Single_Instance() {}Single_Instance(const Single_Instance & s){}
public:Single_Instance *get_Instance(){Single_Instance *tmp = instance.load(std::memory_order_relaxed);std::atomic_thread_fence(std::memory_order_acquire);if(tmp == NULL){std::unique_lock<std::mutex> uniqueLock(mutex_lock);tmp = instance.load(std::memory_order_relaxed);if(tmp == NULL){tmp = new Single_Instance();std::atomic_thread_fence(std::memory_order_release);instance.store(tmp, std::memory_order_relaxed);}}return tmp;}
};

相关文章:

C++设计模式:单例模式(十)

1、单例设计模式 单例设计模式&#xff0c;使用的频率比较高&#xff0c;整个项目中某个特殊的类对象只能创建一个 并且该类只对外暴露一个public方法用来获得这个对象。 单例设计模式又分懒汉式和饿汉式&#xff0c;同时对于懒汉式在多线程并发的情况下存在线程安全问题 饿汉…...

openssl3.2 - exp - zlib

文章目录 openssl3.2 - exp - zlib概述笔记命令行实现程序实现备注 - 压缩时无法base64压缩时无法带口令压缩实现 - 对buffer进行压缩和解压缩测试效果工程实现main.cppCOsslZlibBuffer.hCOsslZlibBuffer.cpp总结END openssl3.2 - exp - zlib 概述 客户端和服务端进行数据交换…...

【故事】无人机学习之旅

今天是清明假期最后一天&#xff0c;晚上在看无人机的东西&#xff0c;翻到了欣飞鸽的知乎主页&#xff0c;读了他的一些文章。虽不曾相识&#xff0c;但感觉我们有很多相似的经历&#xff0c;也想记录一下自己的无人机学习之旅。 青铜&#xff1a;从使用开源飞控开始 我在大…...

torch.mean()的使用方法

对一个三维数组的每一维度进行操作 1&#xff0c;dim0 a torch.Tensor([0, 1, 2, 3, 4, 5,6,7]).view(2, 2, 2) print(a) mean torch.mean(a, 0) print(mean, mean.shape) 输出结果&#xff1a; tensor([[[0., 1.], [2., 3.]], [[4., 5.], [6., 7.]]]) tensor([[2., …...

windows安装Redis,Mongo,ES并快速基本掌握开发流程

前言 这里只是一些安装后的基础操作&#xff0c;后期会学习更加深入的操作 基础操作 前言RedisRedis启动idea集成Redisjedis技术 Mongodbwindows版Mongodb的安装idea整合Mongodb ES(Elasticsearch)ESwindows下载ES文档操作idea整合ES低级别ES整合高级别ES整合 Redis Redis是…...

ruoyi-nbcio-plus基于vue3的flowable的自定义业务提交申请组件的升级修改

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://122.227.135.243:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a…...

掌握网络抓取技术:利用RobotRules库的Perl下载器一览小红书的世界

引言 在信息时代的浪潮下&#xff0c;人们对于获取和分析海量网络数据的需求与日俱增。网络抓取技术作为满足这一需求的关键工具&#xff0c;正在成为越来越多开发者的首选。而Perl语言&#xff0c;以其卓越的文本处理能力和灵活的特性&#xff0c;脱颖而出&#xff0c;成为了…...

典型新能源汽车热管理系统方案分析

目前行业具有代表性的热管理系统有PTC电加热方案、热泵方案&#xff08;特斯拉八通阀热泵、吉利直接式热泵&#xff09;、威马的柴油加热方案以及以理想为代表的插电式混动车方案。 小鹏P7整车热管理方案分析&#xff08;PTC电加热方案&#xff09; 小鹏P7作为小鹏汽车的第2款…...

使用Docker部署开源项目FreeGPT35来免费调用ChatGPT3.5 API

Vercel部署FreeGPT35有严重限制&#xff0c;玩玩就好&#xff0c;真用还是得docker。 限制原因: Vercel的流式响应并不是一开始写流&#xff0c;客户端就能立刻收到响应流&#xff0c;而是先写到一个缓冲区&#xff0c;当流关闭才一股脑的流式响应回来(不是实时流) 因此导致: …...

《Linux运维实战:Kylin V10操作系统开启安装软件保留缓存设置》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;Linux运维实战总结 一、操作步骤 1、改系统/etc/yum.conf配置文件&#xff0c;开启安装软件保留缓存设置 [rootecs-90c2-0003 ~]# vim /etc/yum.…...

视频生成技术:从GAN到Latte

GANs Diffusion Model...

机器学习中的激活函数

激活函数存在的意义&#xff1a; 激活函数决定了某个神经元是否被激活&#xff0c;当这个神经元接收到的信息是有用或无用的时候&#xff0c;激活函数决定了对这个神经元接收到的信息是留下还是抛弃。如果不加激活函数&#xff0c;神经元仅仅做线性变换&#xff0c;那么该神经网…...

LinuxAndroid: 旋转编码器input输入事件适配(旋转输入)

rk3588s: 旋转编码器input输入事件适配 基于Android 12 kernel-5.10版本 参考文档&#xff1a; https://blog.csdn.net/szembed/article/details/131551950 Linux 输入设备调试详解&#xff08;零基础开发&#xff09;Rotary_Encoder旋转编码器驱动 通用GPIO为例 挂载input输…...

机器学习和深度学习-- 李宏毅(笔记与个人理解)Day10

Day 10 Genaral GUidance training Loss 不够的case Loss on Testing data over fitting 为什么over fitting 留到下下周哦~~ 期待 solve CNN卷积神经网络 Bias-Conplexiy Trade off cross Validation how to split? N-fold Cross Validation mismatch 这节课总体听下来比较…...

perl 交叉编译

前言 Perl是一种高级、通用、解释型、动态的编程语言。Perl设计的初衷是为了更好地处理文本处理任务&#xff0c;但随着时间的发展&#xff0c;现在它已经变成了一种强大的一般目的编程语言。Perl支持面向过程和面向对象的编程风格。 Perl的特点&#xff1a; 强大的字符串处…...

浅谈.版本管理工具

定义&#xff1a; 版本控制是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史&#xff0c;方便查看更改历史记录&#xff0c;备份以便恢复以前的版本的软件工程技术。 特点&#xff1a; 1、方便用于管理多人协同开发项目 2、并行开发&#xff0c;可实现跨区…...

【汇编语言实战】已知10个整数求最大值

C语言描述该程序流程&#xff1a; #include <stdio.h> int main() {int a[]{11,33,23,54,12,51,2,4,34,45};int maxa[0];for(int i1;i<9;i){if(a[i]>max){maxa[i];}}printf("%d",max); }汇编语言&#xff1a; include irvine32.inc .data arr dword 11…...

在 CentOS 7 上安装 Redis

在 CentOS 7 上安装 Redis 可以通过几个简单的步骤完成。以下是一种常用的方法&#xff1a; 更新系统&#xff1a; 在安装任何新软件之前&#xff0c;最好先更新系统的软件包列表&#xff0c;以确保安装的软件版本是最新的。可以使用以下命令来更新&#xff1a; sudo yum up…...

『51单片机』蜂鸣器

&#x1f6a9; WRITE IN FRONT &#x1f6a9; &#x1f50e; 介绍&#xff1a;"謓泽"正在路上朝着"攻城狮"方向"前进四" &#x1f50e;&#x1f3c5; 荣誉&#xff1a;2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2222年获评…...

计算机视觉 | 基于二值图像数字矩阵的距离变换算法

Hi&#xff0c;大家好&#xff0c;我是半亩花海。本实验基于 OpenCV 实现了二值图像数字矩阵的距离变换算法。首先生成一个 480x480 的黑色背景图像&#xff08;定义黑色为0&#xff0c;白色为1&#xff09;&#xff0c;在其中随机选择了三个白色像素点作为距离变换的原点&…...

国防科技大学计算机基础课程笔记02信息编码

1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制&#xff0c;因此这个了16进制的数据既可以翻译成为这个机器码&#xff0c;也可以翻译成为这个国标码&#xff0c;所以这个时候很容易会出现这个歧义的情况&#xff1b; 因此&#xff0c;我们的这个国…...

Ubuntu系统下交叉编译openssl

一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机&#xff1a;Ubuntu 20.04.6 LTSHost&#xff1a;ARM32位交叉编译器&#xff1a;arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分

一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计&#xff0c;提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合&#xff1a;各模块职责清晰&#xff0c;便于独立开发…...

Git常用命令完全指南:从入门到精通

Git常用命令完全指南&#xff1a;从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

学习一下用鸿蒙​​DevEco Studio HarmonyOS5实现百度地图

在鸿蒙&#xff08;HarmonyOS5&#xff09;中集成百度地图&#xff0c;可以通过以下步骤和技术方案实现。结合鸿蒙的分布式能力和百度地图的API&#xff0c;可以构建跨设备的定位、导航和地图展示功能。 ​​1. 鸿蒙环境准备​​ ​​开发工具​​&#xff1a;下载安装 ​​De…...

32单片机——基本定时器

STM32F103有众多的定时器&#xff0c;其中包括2个基本定时器&#xff08;TIM6和TIM7&#xff09;、4个通用定时器&#xff08;TIM2~TIM5&#xff09;、2个高级控制定时器&#xff08;TIM1和TIM8&#xff09;&#xff0c;这些定时器彼此完全独立&#xff0c;不共享任何资源 1、定…...

ubuntu中安装conda的后遗症

缘由: 在编译rk3588的sdk时&#xff0c;遇到编译buildroot失败&#xff0c;提示如下&#xff1a; 提示缺失expect&#xff0c;但是实测相关工具是在的&#xff0c;如下显示&#xff1a; 然后查找借助各个ai工具&#xff0c;重新安装相关的工具&#xff0c;依然无解。 解决&am…...

Qt Quick Controls模块功能及架构

Qt Quick Controls是Qt Quick的一个附加模块&#xff0c;提供了一套用于构建完整用户界面的UI控件。在Qt 6.0中&#xff0c;这个模块经历了重大重构和改进。 一、主要功能和特点 1. 架构重构 完全重写了底层架构&#xff0c;与Qt Quick更紧密集成 移除了对Qt Widgets的依赖&…...

n8n:解锁自动化工作流的无限可能

在当今快节奏的数字时代&#xff0c;无论是企业还是个人&#xff0c;都渴望提高工作效率&#xff0c;减少重复性任务的繁琐操作。而 n8n&#xff0c;这个强大的开源自动化工具&#xff0c;就像一位智能的数字助手&#xff0c;悄然走进了许多人的工作和生活&#xff0c;成为提升…...

大陆4D毫米波雷达ARS548调试

本文介绍了大陆ARS548毫米波雷达的调试与测试流程&#xff0c;主要包括以下内容&#xff1a; 设备参数&#xff1a;最大检测距离301m&#xff08;可调93-1514m&#xff09;&#xff0c;支持gPTP时间同步。 接线调试&#xff1a; Windows需使用USB-RJ45转换器 Linux可直接连接网…...