C++多线程:Atomic原子类与CAS锁详解(十)

1、原子操作的概念

什么是原子操作:

  • 原子被认为是构成物质最小的单位,是不可分割的一个东西。
  • 而在程序中原子操作被认为是不可分割的一个步骤或者指令
  • 其实我们很简单的程序,在高级语言中被认为是一个步骤的操作,编译成汇编指令之后却是一个非原子操作,即生成多次指令集操作
  • 而多个指令集组合构成的一个高级语言的操作在执行的时候需要一条一条指令去执行
  • 每条执行完毕是有可能被操作系统调度中断,因此这就会导致线程数据安全问题。
  • 因此为了解决这个非线程安全的操作问题,可以引入一系列的方式:互斥锁(mutex)、同步、信号量、atomic原子操作等
2、高级语言代码与汇编指令

首先看一段函数代码

  • objdump -DC add.o:先编译成.o文件,然后执行这个命令进行反汇编
  • 左边是一个函数,简单的进行a++的执行并且返回
  • 右边是这个函数对应的汇编指令集,看不懂没关系,待会就会懂个七七八八了。
int add(int a){
a++;
return a;
}
0000000000000000 <add(int)>:
push %rbp
mov %rsp,%rbp
/*
这三个指令是完成一个a++操作的所有指令
mov %edi,-0x4(%rbp)
addl $0x1,-0x4(%rbp)
mov -0x4(%rbp),%eax
*/
pop %rbp
retq
  • 首先进行一些名词解释:
    • 首先一个函数的调用都是一个一个的栈帧,而一个栈帧由rbp和rsp分别指向这个栈帧的底部(栈底)和顶部(栈顶)
    • rbp寄存器:栈帧基址指针(Base Pointer),指向这个栈帧的底部(栈底)
    • rsp寄存器:栈指针(Stack Pointer),指向这个栈帧的顶部(栈顶)
    • mov:移动指令
    • addl:加法指令
    • pop:弹出栈指令
    • retq:返回return对应的指令
2.1、函数的调用过程

假设存在一个main函数主体和一个函数add,在main中调用add函数,栈帧的变化过程看下图
在这里插入图片描述

2.2、汇编指令集解析
0000000000000000 <add(int)>:
1   push   %rbp
2   mov    %rsp,%rbp
3   mov    %edi,-0x4(%rbp)
4   addl   $0x1,-0x4(%rbp)
5   mov    -0x4(%rbp),%eax
6   pop    %rbp
7  retq
  • 首先看第一个问题,rbp怎么跳转上去的:这一步就有点像爬楼梯的左脚(rbp)踩右脚(rsp)

    • 创建一个新的栈帧(函数调用)时会将当前栈帧的值存放在栈帧上,对应指令:push %rbp(1)

    • 创建一个新的栈帧,这里创建一个新的栈帧其实很简单,只需要从rsp的位置向上开始创建即可,因此对应指令:mov %rsp,%rbp,意思将rsp的值赋给rbp(2)

    • 再将参数a的值存放在%rbp-0x4的地方,对应指令:mov %edi,-0x4(%rbp)(3)

    • 然后再将1这个值加到%rbp-0x4的地方,对应指令:addl $0x1,-0x4(%rbp)(4)

    • 最后将%rbp-0x4这个地址存放的值放到%eax寄存器中,对应指令:mov -0x4(%rbp),%eax(5)

    • 计算完毕开始弹出新的栈帧(add函数)的栈帧首地址(6)

    • 返回%eax寄存器中的值(7)

  • 可以看到a++这一个步骤需要3条汇编指令,因此a++操作在高级语言中并不是一个原子操作,在多线程下并不安全。

在这里插入图片描述

3、atomic原子类
  • 经过上面的分析,可以清楚的发现多线程情况下的a++并不是安全稳定的,核心原因就是其是一个非原子操作。

  • C++11中提供了atomic头文件,和对应的atomic原子类操作,用于可以保证基础数据类型的这种操作的多线程数据安全的稳定性。

  • atomic原子类只能操作基本的数据类型,对于自定义的数据类型是无法使用的,这一点底层源码都有展示。

3.1、非线程安全操作演示

执行了五次没有一次数据是正确的,可能是我脸太黑了,也有可能是cpu太繁忙,其实最主要的是因为线程不安全。

#include <iostream>
#include <atomic>
#include <thread>int a;
void thread_func()
{for(int i = 0;i < 100000;i++){a++;}
}int main()
{for(int i = 0;i < 5;i++){a = 0;std::thread thread1(thread_func);std::thread thread2(thread_func);thread1.join();thread2.join();std::cout << "第" << i << "次结束时: a = " << a << std::endl;}return 0;
}

在这里插入图片描述

3.2、atomic原子类使用
  • 解决这个方法很多,这里主要使用atomic原子操作去解决这个问题,对于使用一些其他手段就不在讨论的范围内(如互斥锁、同步等)
  • 只需要将int类型的a定义成一个atomic原子int类型的a即可将问题解决。
  • 对比mutex和一些其他的线程安全手段,atomic的最大优势就在于其性能优越、效率更高。
std::atomic<int> a;
void thread_func()
{for(int i = 0;i < 100000;i++){a++;}
}int main()
{for(int i = 0;i < 5;i++){a = {0};std::thread thread1(thread_func);std::thread thread2(thread_func);thread1.join();thread2.join();std::cout << "第" << i << "次结束时: a = " << a << std::endl;}return 0;
}
4、CAS锁
  • 上面介绍了atomic原子类的使用,实际上atomic原子类的底层操作是一个CAS锁进行一个支撑,而CAS是硬件层面保证。

  • CAS锁:CAS的全称是Compare and Swap(比较并交换),经常听到的另外一个名字乐观锁就是这个东西。

  • CAS实现原理:主要设计三个值:旧的预期值A,内存地址值V,新值B。

    • 首先传入旧的预期值A去跟内存中地址值V比较
    • 如果A == V,那么把V用B的值进行替换
    • 如果A != V,那么不做修改,继续重来
      在这里插入图片描述
4.1、ABA问题
  • CAS锁绕不开的一个话题,就是ABA问题:就是如果你有一杯水放在桌子上重100g,先被A倒掉了10g,再被B加入了10g其他物质,最后这杯水重还是10g,但是这杯水已经不能喝了(谁也不知道加了什么),即数据被多次操作,但操作后值还是原来的值

  • 因为CAS 需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS 进行检查时会发现它的值没有发生变化,但是实际上却变化了。

  • 解决这个问题可以加入版本号,如果版本号不对就不会进行赋值:可以采用AtomicMarkableReference,AtomicStampedReference进控制

    • AtomicMarkableReference:用于标记这个值是否被修改,修改过就是true,没有修改过就是false,而不管你修改过几次
    • AtomicStampedReference:用于标记这个值修改过多少次,动一次加一次。
4.2、atomic底层CAS源码实现
  • 主要分为weak和strong两大类版本
  • weak版本的CAS允许偶然出乎意料的返回(比如在字段值和期待值一样的时候却返回了false),不过在一些循环算法中,这是可以接受的。通常它比起strong有更高的性能。
bool compare_exchange_weak(_Tp& __e, _Tp __i, memory_order __s, memory_order __f) noexcept{return __atomic_compare_exchange(std::__addressof(_M_i),std::__addressof(__e), std::__addressof(__i), true, __s, __f);
}bool compare_exchange_weak(_Tp& __e, _Tp __i, memory_order __s, memory_order __f) volatile noexcept{return __atomic_compare_exchange(std::__addressof(_M_i), std::__addressof(__e), std::__addressof(__i), true, __s, __f);
}bool compare_exchange_weak(_Tp& __e, _Tp __i, memory_order __m = memory_order_seq_cst) noexcept{return compare_exchange_weak(__e, __i, __m, __cmpexch_failure_order(__m));
}bool compare_exchange_weak(_Tp& __e, _Tp __i, memory_order __m = memory_order_seq_cst) volatile noexcept{return compare_exchange_weak(__e, __i, __m, __cmpexch_failure_order(__m));
}bool compare_exchange_strong(_Tp& __e, _Tp __i, memory_order __s, memory_order __f) noexcept{return __atomic_compare_exchange(std::__addressof(_M_i),std::__addressof(__e), std::__addressof(__i), false, __s, __f);
}bool compare_exchange_strong(_Tp& __e, _Tp __i, memory_order __s, memory_order __f) volatile noexcept{return __atomic_compare_exchange(std::__addressof(_M_i), std::__addressof(__e), std::__addressof(__i), false, __s, __f);
}bool compare_exchange_strong(_Tp& __e, _Tp __i, memory_order __m = memory_order_seq_cst) noexcept{return compare_exchange_strong(__e, __i, __m, __cmpexch_failure_order(__m)); 
}bool compare_exchange_strong(_Tp& __e, _Tp __i, memory_order __m = memory_order_seq_cst) volatile noexcept{return compare_exchange_strong(__e, __i, __m, __cmpexch_failure_order(__m)); 
}

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

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

相关文章

Redis从入门到精通(三)Jedis客户端、SpringDataRedis客户端

文章目录 前言第3章 Redis的Java客户端3.1 Jedis客户端3.1.1 快速使用3.1.2 连接池 3.2 SpringDataRedis客户端3.2.1 快速使用3.2.2 自定义序列化3.2.3 StringRedisTemplate 3.3 小结 前言 在上一章【Redis从入门到精通(二)Redis的数据类型和常见命令介绍】中&#xff0c;学习…

Springboot+MybatisPlus+EasyExcel实现文件导入数据

记录一下写Excel文件导入数据所经历的问题。 springboot提供的文件处理MultipartFile有关方法&#xff0c;我没有具体看文档&#xff0c;但目测比较复杂&#xff0c; 遂了解学习了一下别的文件上传方法&#xff0c;本文第1节记录的是springboot原始的导入文件方法写法&#xf…

docker-compse安装es(包括IK分词器扩展)、kibana、libreoffice

Kibana是一个开源的分析与可视化平台&#xff0c;设计出来用于和Elasticsearch一起使用的。你可以用kibana搜索、查看存放在Elasticsearch中的数据。 Kibana与Elasticsearch的交互方式是各种不同的图表、表格、地图等&#xff0c;直观的展示数据&#xff0c;从而达到高级的数据…

MySQL 优化及故障排查

目录 一、mysql 前置知识点 二、MySQL 单实例常见故障 故障一 故障二 故障三 故障四 故障五 故障六 故障七 故障八 三、MySQL 主从故障排查 故障一 故障二 故障三 四、MySQL 优化 1.硬件方面 &#xff08;1&#xff09;关于 CPU &#xff08;2&#xff09;关…

Reversing Linked List

Given a constant K and a singly linked list L, you are supposed to reverse the links of every K elements on L. For example, given L being 1→2→3→4→5→6, if K3, then you must output 3→2→1→6→5→4; if K4, you must output 4→3→2→1→5→6. Input Specifi…

网络基础——ISIS

名词 ISIS&#xff1a;中间系统到中间系统&#xff0c;优先级是15集成化ISIS&#xff1a;这是在优化后&#xff0c;可以使用在OSI模型上的NET地址&#xff1a;由区域ID、系统ID和SEL组成&#xff0c;一台设备上最多配置3个NET地址&#xff0c;条件是区域号要不一致&#xff0c;…

Intel FPGA (7):adc adc128s102

Intel FPGA (7)&#xff1a;adc adc128s102 前提摘要 个人说明&#xff1a; 限于时间紧迫以及作者水平有限&#xff0c;本文错误、疏漏之处恐不在少数&#xff0c;恳请读者批评指正。意见请留言或者发送邮件至&#xff1a;“Email:noahpanzzzgmail.com”。本博客的工程文件均存…

用Python实现办公自动化(自动化处理PDF文件)

自动化处理 PDF 文件 目录 自动化处理 PDF 文件 谷歌浏览器 Chrome与浏览器驱动ChromeDriver安装 &#xff08;一&#xff09;批量下载 PDF 文件 1.使用Selenium模块爬取多页内容 2.使用Selenium模块下载PDF文件 3.使用urllib模块来进行网页的下载和保存 4.使用urllib…

关于OcenaBase v4.2中,分区转移和负载均衡的技术解读

OceanBase​​​​​​​​​​​​​​作为一款原生分布式数据库&#xff0c;其核心的技术特性之一是高可扩展性&#xff0c;其具体表现在两个方面&#xff1a; 首先&#xff0c;是灵活的扩缩容能力&#xff0c;包括垂直扩缩容和水平扩缩容&#xff1a; 垂直扩缩容&#xff…

神经网络汇聚层

文章目录 最大汇聚层平均汇聚层自适应平均池化层 最大汇聚层 汇聚窗口从输入张量的左上角开始&#xff0c;从左往右、从上往下的在输入张量内滑动。在汇聚窗口到达的每个位置&#xff0c;它计算该窗口中输入子张量的最大值或平均值。计算最大值或平均值是取决于使用了最大汇聚…

RISC-V/ARM mcu OpenOCD 调试架构解析

Risc-v/ARM mcu OpenOCD 调试架构解析 最近有使用到risc-v的单片机&#xff0c;所以了解了下risc-v单片机的编译与调试环境的搭建&#xff0c;面试时问到risc-v的调试可参看以下内容。 risc-v根据官方的推荐&#xff0c;调试器服务是选择OpenOCD&#xff0c;DopenOCD(开放片上…

Python反爬案例——验证码的识别

验证码的识别 使用打码平台识别验证码 利用打码平台可以轻松识别各种各样的验证码&#xff0c;图形验证码、滑动验证码、点选验证码和逻辑推理验证码。打码平台提供了一系列API&#xff0c;只需要向API上传验证码图片&#xff0c;它便会返回对应的识别结果。 使用超级鹰平台…

深入理解指针1:指针变量、指针运算、野指针、const修饰指针

生活中我们把门牌号也叫地址&#xff0c;在计算机中我们把内存单元的编号也称为地址。C语⾔中给地址起 了新的名字叫&#xff1a;指针。 所以我们可以理解为&#xff1a;内存单元的编号地址指针 1、指针变量 我们知道的是&#xff1a;数组名是数组首元素的地址。也就是说&…

中断服务程序模板

通常定时器初始化过程如下: ①对 TMOD赋值,以确定TO和T1的工作方式。 ②计算初值,并将初值写入THO、TLO或TH1、TL1。 ③中断方式时&#xff0c;则对IE赋值&#xff0c;开放中断。 ④使TRO或TR1置位&#xff0c;启动定时器/计数器定时或计数。 代码 利用定时器0工作方式1&…

轻松设置Facebook自动隐藏评论和删除评论功能

Facebook作为海外营销的最大流量平台之一&#xff0c;是很多跨境卖家争夺的市场&#xff0c;希望可以通过Facebook这个全球性的平台来推广自己的产品或服务。身处这个竞争激烈的市场&#xff0c;任何一条负面评论或不当言论出现在你的品牌页面上都可能影响到品牌形象&#xff0…

臻奶惠无人售货机:新零售时代的便捷消费革命

臻奶惠无人售货机&#xff1a;新零售时代的便捷消费革命 在新零售的浪潮中&#xff0c;智能无人售货机作为一个创新的消费模式&#xff0c;已经成为距离消费者最近的便捷购物点之一。这种模式不仅能够满足居民对消费升级的需求&#xff0c;还能通过建立多样化和多层次的消费体…

k8s练习-创建一个Deployment

创建Deployment 创建一个nginx deployment [rootk8s-master home]# cat nginx-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata:name: nginx-deployment spec:selector:matchLabels:app: nginx # 配置pod的labelsreplicas: 2 # 声明2个副本template:metada…

spring boot自动配置原理-怎样回答这个问题

首先我们说一下自动配置的概念。 自动配置&#xff1a;遵循约定大约配置的原则&#xff0c;在boot程序启动后&#xff0c;起步依赖中的一些bean对象会自动注入到ioc容器 例子 程序引入spring-boot-starter-web 起步依赖&#xff0c;启动后&#xff0c;会自动往ioc容器中注入…

记一次 pdfplumber 内存泄漏导致的服务器宕机

有一个项目需求&#xff0c;要在每天凌晨5点的时候执行一个任务&#xff0c;获取一系列的PDF文件并解析。 后端是Django框架&#xff0c;定时任务用Celery来实现的。 本地跑没什么问题&#xff0c;但是一放到服务器上跑就会宕机&#xff0c;而且是毫无征兆的宕机&#xff0c;…

黑马HTMLCSS基础

黑马的笔记和资料都是提供好了的&#xff0c;这个文档非常适合回顾复习。我在黑马提供的笔记上做了一些微不足道的补充&#xff0c;以便自己复习查阅。该笔记比较重要的部分是 表单&#xff0c;http请求 第一章. HTML 与 CSS HTML 是什么&#xff1a;即 HyperText Markup lan…