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,基本上会导致这样的结果:要么无法编写多 ...
随机推荐
- Java I/O 教程(五) BufferedOutputStream 类
Java BufferedOutputStream Class Java BufferedOutputStream class 用于缓冲一个输出流 其内部使用缓冲区存储数据,可以更有效率的往流中写入数 ...
- 并发慎用——System.currentTimeMillis()
好记忆不如烂笔头,能记下点东西,就记下点,有时间拿出来看看,也会发觉不一样的感受. System.currentTimeMillis()是极其常用的基础Java API,广泛地用来获取时间戳或测量代码 ...
- go语言range语句中的值的坑
在range语句中生成的数据的值是真实集合元素的副本,它们不是原有元素的引用.这意味着更新这些值将不会 修改原来的数据,同时也意味着使用这些值的地址将不会得到原有数据的指针. package main ...
- 【Azure App Service for Container】记一次拉取镜像失败的特殊情况
问题描述 使用Azure App Service For Container 拉取 应用镜像,发现拉取失败. 错误消息: "Image pull failed since Inspect i ...
- 面试官上来就让手撕HashMap的7种遍历方式,当场愣住,最后只写出了3种
写在开头 今天有个小伙伴私信诉苦,说面试官上来就让他手撕HashMap的7种遍历方式,最终只写出3种常用的,怀疑面试官是在故意***难.这个问题大家怎么看? 反正我个人感觉这肯定不是***难,&quo ...
- mysql-查询库中所有表名称或者某一张表的所有字段名称
-- 查询某一库中所有表的名称, SELECT a.TABLE_SCHEMA ,a.TABLE_NAME ,a.TABLE_COMMENT FROM information_schema.TABLES ...
- MYSQL索引-上
前言 今天同事做数据清理的时候发现我这边有一张表没有主键,这个表有两个字段,ID和Name,ID作者唯一索引,在我印象里,一个数据表如果没有主键索引,它会内部创建主键索引,创建的标准就是唯一性,我觉得 ...
- Zabbix“专家坐诊”第195期问答汇总
问题一 Q:麻烦请教一下zabbix服务器总是上报这几个告警,需要处理嘛?怎么处理? A:同步历史数据进程负载过高的话会影响到server的性能,建议增加服务器硬件配置. Q:是需要增加哪方面的配置, ...
- canvas-screenshot 视频截屏功能,选择视频的一个区域,进行截图
预览地址:http://pengchenggang.gitee.io/canvas-screenshot/ 参考资料:https://www.canvasapi.cn/ <!DOCTYPE ht ...
- 基于stm32H730的解决方案开发之点亮第一个LED灯
一 概述 STM32H730超值系列内含ArmCortex-M7内核(具有双精度浮点单元),工作频率可达550 MHz.内嵌的128 KB闪存使意法半导体能够为开发人员提供一种经济划算的解决方案.凭借 ...