C++ 中的 volatile 和 atomic
C++ 中的 volatile 和 atomic
0. TL;DR
std::atomic 用于多线程并发场景,有两个典型使用场景:
- 原子操作:对 atomic 变量的操作(读/写/自增/自减)仿佛受互斥量保护。一般通过特殊的机器指令实现,比使用互斥量更高效
- 限制编译器/硬件对赋值操作的重新排序
volatile 和多线程并发没有任何关系,用于防止编译器优化掉对特殊变量的“冗余”读写操作
1. Data Race 和未定义行为
C++ 中有很多未定义行为,Data Race 便是其中之一。
️ 如果一个变量不是 atomic 变量,且没有互斥量保护,在一个线程中执行写操作,同时在另一个线程中读取,则会产生 Data Race,其行为未定义!
例如在执行下面代码的过程中,另一个线程同时读取 i 的值,读到的值可能是 -13,42,0,43923... 任何值!
int i = 0;
++i;
--i;
虽然在这种场景下,你读到的大概率是0/1,但要知道,对于未定义行为,理论上可能读到任何值!!
2. atomic
std::atomic 是 C++ 中的模版类,一般用于 bool、整型、指针类型,如 atomic<bool>,atomic<int>,atomic<Widget*> 等。对 atomic 变量的操作(读/写/自增/自减)仿佛受互斥量保护(底层一般通过特殊的机器指令实现,比使用互斥量更高效)。
2.1 原子操作
atomic 的第一个应用场景就是多线程读写变量:
atomic<int> ai {0};
ai = 10; // 原子写
std::cout << ai; // 原子读
++ai; // 原子自增为11
--ai; // 原子自减为10
执行上述代码期间,如果在另一个线程读取 ai 的值,只可能读到 0/10/11,不可能有其他结果。
2.2 限制重排序
atomic 的第二个应用场景是,当某个变量在两个任务之间传递信息时,防止对该变量赋值进行重新排序。假设 a、b、x、y 是 4 个独立的变量:
a = b;
x = y;
为了提升性能,编译器可能会对不相关的赋值进行重新排序为:
x = y;
a = b;
即使编译器不这么做,底层硬件也可能这么做。atomic 可以避免这种重排序。例如在一个任务中计算 value,另一个任务中依赖 value 的可用性,则可以借助 atomic 变量实现:
atomic<bool> valueAvailable(false);
auto value = computeValue();
valueAvailable = true;
std::atomic 默认采用顺序一致性模型,会限制重新排序:不仅编译器会保证 value 在 valueAvailable 之前赋值,底层硬件也保证这个顺序。
️ C++ 还提供了其他更灵活的一致性模型(如 memory_order_relaxed),除非你是这方面的专家,很清楚不同内存序产生的影响,否则不要轻易使用。
2.3 load/store
有的开发者喜欢使用 load()/store() 成员函数,这不是必须,但可以起到强调作用:
- 强调该变量涉及多线程并发操作
- 强调 atomic 变量可能导致性能问题
- 虽然 atomic 比互斥量更高效,但仍然比普通变量慢、开销大
- atomic 变量可能会阻止重新排序优化
3. volatile
如果将上面的例子中的 atomic<bool> 换成 volatile bool,既无法保证操作的原子性,也无法限制对 value 和 valueAvailable 赋值的重新排序。
volatile bool valueAvailable(false);
auto value = computeValue();
valueAvailable = true;
对于普通内存来说,如果向某个内存写一个值,该值会一直保留在内存,直到被下一个写操作覆盖。编译器可以对普通内存的变量读写进行优化,例如下面这段代码(虽然一般开发者不会直接写出这样的代码,但是经过模版实例化、内联、重排序等优化后,很可能产生类似的代码):
int x;
auto y = x; // 读取 x
y = x; // 再次读取 x
x = 10; // 写入 x
x = 20; // 再次写入 x
可能被优化为:
auto y = x;
x = 20;
但是在有些特殊场景下(例如和外设交互),变量对应特殊的内存区域:
x 可能是一个传感器的值,连续两次读取 x 的值可能不同,不应该被优化
x 可能对应某个无线电发射器的控制端口,
x = 10和x = 20对应两条不同的指令,不应该被优化
volatile 的作用正是告诉编译器,某个变量所对应内存是特殊内存,不要进行任何优化!
将 x 声明为 volatile int x 即可避免上述优化,而声明为 atomic<int> x 则没有这个效果。
️ 经常听到一种说法:开启编译器优化选项之后,会导致程序行为异常。但通常都不是编译器的问题,而是自己的代码不规范,或依赖了未定义行为导致的!
思考:
- volatile 和 atomic 可以同时使用吗,用于什么场景?
- volatile 和 const 可以同时使用吗,用于什么场景?
4. Reference
《Effective Morden C++》条款 40
C++ 中的 volatile 和 atomic的更多相关文章
- zz剖析为什么在多核多线程程序中要慎用volatile关键字?
[摘要]编译器保证volatile自己的读写有序,但由于optimization和多线程可以和非volatile读写interleave,也就是不原子,也就是没有用.C++11 supposed会支持 ...
- java中的volatile关键字
java中的volatile关键字 一个变量被声明为volatile类型,表示这个变量可能随时被其他线程改变,所以不能把它cache到线程内存(如寄存器)中. 一般情况下volatile不能代替syn ...
- C/C++中的volatile简单描述
首先引入一篇博客: 1. 为什么用volatile? C/C++ 中的 volatile 关键字和 const 对应,用来修饰变量,通常用于建立语言级别的 memory barrier.这是 BS 在 ...
- C和C++中的volatile、内存屏障和CPU缓存一致性协议MESI
目录 1. 前言2 2. 结论2 3. volatile应用场景3 4. 内存屏障(Memory Barrier)4 5. setjmp和longjmp4 1) 结果1(非优化编译:g++ -g -o ...
- [读书笔记]java中的volatile关键词
以下内容大多来自周志明的<深入理解Java虚拟机>. 当一个变量被volatile修饰后,它将具备两种特性: 1. 保证此变量对所有线程的可见性,这里的“可见性”是指当一条线程修改了这个变 ...
- C语言中关键字volatile的含义【转】
本文转载自:http://m.jb51.net/article/37489.htm 本篇文章是对C语言中关键字volatile的含义进行了详细的分析介绍,需要的朋友参考下 volatile 的意思是“ ...
- java多线程中的volatile和synchronized
package com.chzhao; public class Volatiletest extends Thread { private static int count = 0; public ...
- java中的Volatile 变量
Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 synchronized”:与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少, ...
- 单例模式中的volatile关键字
在之前学习了单例模式在多线程下的设计,疑惑为何要加volatile关键字.加与不加有什么区别呢?这里我们就来研究一下.单例模式的设计可以参考个人总结的这篇文章 背景:在早期的JVM中,synchr ...
- Java中的volatile关键字的功能
Java中的volatile关键字的功能 volatile是java中的一个类型修饰符.它是被设计用来修饰被不同线程访问和修改的变量.如果不加入volatile,基本上会导致这样的结果:要么无法编写多 ...
随机推荐
- Spring Boot学生信息管理系统项目实战-4.学生管理
1.获取源码 源码是捐赠方式获取,详细请QQ联系我 :) 2.实现效果 2.1 导出导入模板 2.2 导入学生数据 3.项目源码 只挑重点讲,详细请看源码. 学生管理包含了学生信息的增删改查,这里我只 ...
- 解决putty连接报 connection refused
Ubuntu中换个速度快点的源后 执行 $sudo apt-get install openssh-server 安装ssh协议 执行ifconfig显示Ubuntu的ip地址 xp中用putty输入 ...
- 升级 vcpkg 遇到的一些坑
项目上有个需求要用到 wil 库,于是打开 cmd 输入: vcpkg install wil:x86-windows-static 等了很久,一直卡在配置命令 连续试了好几遍,还是不行,安装其他的静 ...
- 迁移到 Gradle 7.x 使用 Version Catalogs 管理依赖
一.根目录下 build.gradle 变更 变更前: buildscript { ext.kotlin_version = '1.5.0' repository { repository { mav ...
- 学会了Java 8 Lambda表达式,简单而实用
OneAPM 摘要:此篇文章主要介绍Java8 Lambda 表达式产生的背景和用法,以及 Lambda 表达式与匿名类的不同等.本文系OneAPM工程师编译整理. Java是一流的面向对象语言,除了 ...
- 一次kafka消息丢失问题处理
背景&现象 生产微服务架构环境,kafka消息消费服务架构如下: 当服务B接口出现宕机或者B接口调用超时,kafka消息消费端服务A出现异常,异常发生后未执行手动提交offset操作.待服务B ...
- 【Azure 存储服务】Azure Blob下面是否可以创建子文件夹
问题描述 如何在Azure Storage Account(存储账户) 门户上为 Container 创建子文件夹? 问题解决 经验证,没有办法在门户上直接创建文件夹,不过可以使用Azure Stor ...
- CXPACKET等待类型分析
背景 客户反馈今天8点钟开始进入业务高峰期后,数据库的CPU利用率非常高,基本达到了100%,前端应用也非常慢.怀疑是昨晚业务系统升级导致,请我们紧急协助分析. 现象 登录到SQL专家云,进入相关时间 ...
- mysql添加联合唯一索引与删除索引
-- 添加联合唯一索引 alter table <表名> add unique index <索引名称> (name, no, org_id); -- 删除索引 ALTER T ...
- C++ //谓词 //一元谓词 //概念:返回bool类型的仿函数称为 谓词 //如果 operator()接受一个参数,那么叫做一元谓词 //如果 operator()接受 2 个参数,那么叫做一元谓词
1 //谓词 2 //一元谓词 3 //概念:返回bool类型的仿函数称为 谓词 4 //如果 operator()接受一个参数,那么叫做一元谓词 5 //如果 operator()接受 2 个参数, ...