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

C++多线程:单例模式与共享数据安全(七)

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

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

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

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

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

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

2、单线程下的懒汉模式
2.1、单例对象的创建:
  • 将类指针对象进行静态私有化,并且在类外初始化这个对象为空;静态能保证的是这个对象属于这个类不属于任何一个对象
  • 私有化空构造器防止可以实例化对象
  • 对外暴露一个public方法获取该对象,如果在获取时发现该对象为空,那么进行实例化,否则直接返回
  • 因此可以看到实例化只有一次,多次获取到的对象的地址属于同一个
class Single_Instance {
private:static Single_Instance *instance;Single_Instance() {}
public:static Single_Instance *get_Instance(){if(instance == NULL){instance = new Single_Instance();}return instance;}void func(){std::cout << "func(), &instance = " << instance << std::endl;}
};
Single_Instance *Single_Instance::instance = NULL;void test1()
{Single_Instance *instance1 = Single_Instance::get_Instance();Single_Instance *instance2 = Single_Instance::get_Instance();instance1->func();instance2->func();
}# 输出
func(), &instance = 0x5652eefede70
func(), &instance = 0x5652eefede70
2.2、单例对象的析构
  • 很明显上面的代码缺少一个析构函数,并且似乎无从下手找一个合适的时机对其进行析构,只能等待程序运行结束操作系统回收?
  • 其实可以通过内部类的方式进行析构
    • 首先在单例类内部进行私有化一个内部类
    • 对外暴露的public获取instance的对象接口在new实例化对象的时候创建一个内部类静态成员
    • 内部类静态成员的好处是只有一份
    • 当作用域结束时内部类就会负责析构掉主类的静态成员对象
class Single_Instance {
private:static Single_Instance *instance;Single_Instance() {}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 test1()
{Single_Instance *instance1 = Single_Instance::get_Instance();Single_Instance *instance2 = Single_Instance::get_Instance();instance1->func();instance2->func();
}
#输出
func(), &instance = 0x558eb768de70
func(), &instance = 0x558eb768de70
inner_class::~inner_class(), 析构Single_Instance::instance对象
3、单例模式与多线程
  • 单例模式的对象可能会被多个线程使用,但是又必须保证这个单例的对象只有一份

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

class Single_Instance {
private:static Single_Instance *instance;Single_Instance() {}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;
class Single_Instance {
private:static Single_Instance *instance;Single_Instance() {}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(){std::unique_lock<std::mutex> uniqueLock(mutex_lock);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();
}
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线程认为对象已经实例化完毕、其实对象并没有完成初始化!产生错误

  • 但这个问题在C++11中已经禁止了重排序,因此不需要使用volatile关键字,但在Java和一些其他语言中可能有,Java中这个关键字是针对即时编译器JIT进行指令重排序的

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;
}

只需要把上面的代码改成这个样子即可

4、std::call_once()
  • std::call_once()是C++11引入的函数,该函数的功能就是保证一个方法只会被调用一次。

    • 参数二:一个函数名func

    • 参数一:std::once_flag一个标记,本质是一个结构体。该标志可以用于标记参数二该函数是否已经调用过了

    • 参数三:参数二函数的参数

  • std::call_once()具有互斥量的这种能力,且效率上比mutex互斥量效率更高,因此也可以使用这个函数对单例的线程安全进行保证

    • 当call_once调用过一次之后,std::once_flag将会被修改标记(已调用),那么之后都不会在调用
  • 下面看个代码举例,可以看到create_Instance()函数中对于这个函数只执行了一次,完全ojbk。

class Single_Instance {
private:static Single_Instance *instance;static std::once_flag instance_flag;Single_Instance() {}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 void create_Instance(){instance = new Single_Instance();static inner_class innerClass;}static Single_Instance *get_Instance(){std::call_once(instance_flag, create_Instance);return instance;}void func(){std::cout << "func(), &instance = " << instance << std::endl;}
};
Single_Instance *Single_Instance::instance = NULL;
std::once_flag Single_Instance::instance_flag;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 test3()
{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();
}

在这里插入图片描述

相关文章:

C++多线程:单例模式与共享数据安全(七)

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

康耐视visionpro-CogAcqFifoTool工具详细说明

CogAcqFifoTool操作说明&#xff1a; ① 打开工具栏&#xff0c;双击或点击鼠标拖拽 添加CogAcqFifoTool ②.从图片采集设备/图像采集卡列表里选择对应的相机&#xff0c;视频格式选择图像格式。 Mono表示黑白图像&#xff0c;RGB表示彩色相机。点击初始化取相初始化相机。 ③…...

静态图片如何生成gif动画?一个网站在线实现

在当下这个媒体时代&#xff0c;各种各样的图片充斥着我们的生活。尤其是gif动图能够快速有效的传递信息&#xff0c;让用户更加直观的了解某个时间或是场景。非常的生动便捷&#xff0c;那么怎么弄制作gif动画图片呢&#xff1f;其实&#xff0c;只是gif动画的方法非常的简单&…...

Git 实战教程

Git 是一款强大的分布式版本控制系统&#xff0c;广泛用于团队协作与项目管理。本文将为你提供一份 Git 的实战教程&#xff0c;通过实例演示 Git 的基本用法和高级特性&#xff0c;帮助你快速上手 Git。 一、Git 基础 安装 Git 首先&#xff0c;你需要在你的计算机上安装 G…...

解决Vue中仓库持久化的问题,不借助插件用原生JS实现仓库持久化。了解仓库的插件机制、监听的时机

1、演示 前言&#xff1a;目前Vue有两种仓库&#xff0c;一种是Vuex&#xff0c;一种是Pinia&#xff0c;懂得都懂&#xff0c;这里就不详细介绍这两者的区别了 2、什么是持久化 仓库里面的数据是需要跨越页面周期的&#xff0c;当页面刷新之后数据还在&#xff0c;在默认情况下…...

ajax的优缺点有哪些?

我们先来介绍一下什么是ajax&#xff1a; 对于ajax的理解&#xff0c;ajax是一种使用现有技术集合技术内容包括: HTML或XHTML、CSS、 JavaScript、DOM、XML、 XSLT&#xff0c; 以及最重要的XMLHttpRequest。 用于浏览器与服务器之间使用异步数据传输(HTTP请求)&#xff0c;做…...

自贡市第一人民医院:超融合与 SKS 承载 HIS 等核心业务应用,加速国产化与云原生转型

自贡市第一人民医院始建于 1908 年&#xff0c;现已发展成为集医疗、科研、教学、预防、公共卫生应急处置为一体的三级甲等综合公立医院。医院建有“全国综合医院中医药工作示范单位”等 8 个国家级基地&#xff0c;建成高级卒中中心、胸痛中心等 6 个国家级中心。医院日门诊量…...

vue使用iview导航栏Menu activeName不生效

activeName不生效 一、问题一、解决方案&#xff0c; 一、问题 根据ivew官网的提示&#xff0c;设置了active-name和open-names以后&#xff0c;发现不管是设置静态是数据还是设置动态的数据&#xff0c;都不生效 一、解决方案&#xff0c; 在设置动态名称的时候&#xff0c…...

谷粒商城实战(008 缓存)

Java项目《谷粒商城》架构师级Java项目实战&#xff0c;对标阿里P6-P7&#xff0c;全网最强 总时长 104:45:00 共408P 此文章包含第151p-第p157的内容 简介 数据库承担落盘&#xff08;持久化&#xff09;工作 拿map做缓存 这种是本地缓存&#xff0c;会有一些问题 分布…...

python的相关语法

Day01 1.Python是什么语言 python是解释性语言&#xff0c;什么为编译&#xff1f;1.生成目标文件&#xff0c;编译型语言在程序执行之前&#xff0c;先会通过编译器对程序执行一个编译的过程&#xff0c;把程序转变成机器语言。运行时就不需要翻译&#xff0c;而直接执行就行。…...

【面试经典150 | 动态规划】最小路径和

文章目录 写在前面Tag题目来源解题思路方法一&#xff1a;动态规划方法二&#xff1a;空间优化 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题…...

生成式AI的情感实验——AI能否产生思想和情感?

机器人能感受到爱吗&#xff1f;这是一个很好的问题&#xff0c;也是困扰了科学家们很多年的科学未解之谜。虽然我们尚未准备好向智能机器赋予情感&#xff0c;但智能机器却已经可以借助生成式人工智能&#xff08;AI&#xff09;来帮助我们表达自己的情感。 自然情感表达 AI正…...

力扣贪心算法--第一天

前言 今天是贪心算法的第一天&#xff0c;算法之路重新开始&#xff01; 内容 之前没了解过贪心算法。 什么是贪心 贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优。难点就是如何通过局部最优&#xff0c;推出整体最优。 一、455.分发饼干 假设你是一…...

Nginx反向代理和缓存

一、Nginx反向代理 1.调度和代理的区别&#xff1a; 1.调度基于内核层面&#xff0c;代理基于应用层面 2.代理必须实现一手托两家 3.调度不需要监听任何端口&#xff0c;不需要工作任何应用程序&#xff0c;代理需要工作和上游服务器一模一样的进程 4.调度没有并发上限&am…...

支持多元AI场景应用,宁畅“NEX AI Lab”开放试用预约中

3月29日&#xff0c;宁畅在京举行发布会&#xff0c;正式发布“全局智算”战略&#xff0c;并在会上推出战略性新品“AI算力栈”&#xff0c;旨在有效解决大模型产业落地的全周期问题。 据宁畅CTO赵雷介绍&#xff0c;“AI算力栈”集成了宁畅在AI计算领域的软硬件能力&#xff…...

Git 如何合并多个连续的提交

我平常的编程喜欢是写一段代码就提交一次&#xff0c;本地一般不攒代码&#xff0c;生怕本地有什么闪失导致白干。但这样就又导致一个问题&#xff1a;查看历史日志时十分不方便&#xff0c;随便找一段提交可以看到&#xff1a; > git log --oneline 8f06be5 add 12/qemu-h…...

k8s 基础入门

1.namespace k8s中的namespace和docker中namespace是两码事&#xff0c;可以理解为k8s中的namespace是为了多租户&#xff0c;dockers中的namespace是为了网络、资源等隔离 2.deployment kubectl create #新建 kubectl aply #新建 更新 升级&#xff1a; 滚动升级&#x…...

【Python项目】AI动物识别工具

目录 背景 技术简介 系统简介 界面预览 背景 成像技术在全球科技发展中扮演了关键角色。在科学研究领域&#xff0c;拍摄所得的图像成为了一种不可或缺的研究工具。特别是在生态学与动物学研究中&#xff0c;鉴于地球的广阔地域和多样的气候条件&#xff0c;利用图像技术捕…...

逻辑回归(Logistic Regression)详解

逻辑回归是一种用于解决二分类问题的统计方法&#xff0c;它通过构建一个模型来预测某个事件的概率。 以下是逻辑回归的一些关键要点&#xff1a; 适用场景&#xff1a;逻辑回归特别适合于处理二分类问题&#xff0c;即两个类别的分类问题&#xff0c;例如判断一封邮件是否为…...

.vimrc文件的语句语法

本文结构&#xff1a; a、简介 b、详细解释其中的一些常见语句和语法。 a、.vimrc 文件是 Vim 编辑器用于配置用户设置和自定义行为的文件。当 Vim 启动时&#xff0c;它会读取 .vimrc 文件中的命令和设置&#xff0c;并根据这些指令来配置编辑器的行为。 b、.vimrc 文件中…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】

微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来&#xff0c;Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

sqlserver 根据指定字符 解析拼接字符串

DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三&#xff0c;HubSpot宣布已构建与ChatGPT的深度集成&#xff0c;这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋&#xff0c;但同时也存在一些关于数据安全的担忧。 许多网络声音声称&#xff0c;这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

AI语音助手的Python实现

引言 语音助手(如小爱同学、Siri)通过语音识别、自然语言处理(NLP)和语音合成技术,为用户提供直观、高效的交互体验。随着人工智能的普及,Python开发者可以利用开源库和AI模型,快速构建自定义语音助手。本文由浅入深,详细介绍如何使用Python开发AI语音助手,涵盖基础功…...

抽象类和接口(全)

一、抽象类 1.概念&#xff1a;如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象&#xff0c;这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法&#xff0c;包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中&#xff0c;⼀个类如果被 abs…...

Matlab实现任意伪彩色图像可视化显示

Matlab实现任意伪彩色图像可视化显示 1、灰度原始图像2、RGB彩色原始图像 在科研研究中&#xff0c;如何展示好看的实验结果图像非常重要&#xff01;&#xff01;&#xff01; 1、灰度原始图像 灰度图像每个像素点只有一个数值&#xff0c;代表该点的​​亮度&#xff08;或…...

Linux基础开发工具——vim工具

文章目录 vim工具什么是vimvim的多模式和使用vim的基础模式vim的三种基础模式三种模式的初步了解 常用模式的详细讲解插入模式命令模式模式转化光标的移动文本的编辑 底行模式替换模式视图模式总结 使用vim的小技巧vim的配置(了解) vim工具 本文章仍然是继续讲解Linux系统下的…...