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,基本上会导致这样的结果:要么无法编写多 ...
随机推荐
- sentry 在加载模块时闪退
这是一个很久之前的问题了,今天记录一下,以便遇到同样问题的同学能够看到此文章 崩溃环境: 目前仅收到 windows 7 的部分用户反馈,在程序启动时发生闪退 问题分析: 查看用户提供的日志,可以看见 ...
- websocket 监听端口问题
有用户反馈使用我们的软件时会出现 ERR_CONNECTION_CLOSED 问题 我们根据反馈排查问题发现是软件的 websocket 监听端口被另一款软件占用了,暂时的解决方法是将占用的软件关闭 ...
- Kubernetes leader election 源码分析
0. 前言 Kubernetes:kube-scheduler 源码分析 介绍了 kube-scheduler 调度 Pod 的逻辑.文中有一点未提的是,在 Kubernetes 集群中,kube-s ...
- Redis集群单机环境搭建
概述 目标:在单台物理机上搭建3主3从共6个节点的Redis集群. 版本:Redis 6.2.4 系统:Ubuntu 18.04 Desktop(IP地址:192.168.100.247) 准备工作 ...
- vscode中跑go test时打印详细信息配置
1.文件>首选项>设置>搜索Test Flags 2.选择在settings.json中编辑 3.添加以下内容 "go.testFlags":[ "-v ...
- go最新版本1.15安装配置及编辑器2020.2版本goland
下载 https://golang.google.cn/dl/ 配置 go env #查看是否安装成功 # 终端输入修改镜像地址 $ go env -w GO111MODULE=on $ go env ...
- 【Azure Key Vault】.NET 代码如何访问中国区的Key Vault中的机密信息(Get/Set Secret)
问题描述 使用 .NET Azure.Identity 中的 DefaultAzureCredential 认证并连接到Azure Key Vault中, 在Key Vault 的示例中,并没有介绍如 ...
- set中的erase使用的一个错误
如果在遍历set的时候去erase很容易出事 事故代码: multiset<int>a; for(auto it=a.begin();it!=a.end();it++){ a.erase( ...
- dos-基础用法
DOS(磁盘操作系统)是一个早期的基于命令行的操作系统,尽管现代操作系统已经发展为图形用户界面(GUI),但是了解和掌握一些基本的DOS命令仍然非常有用,尤其是在处理批处理脚本.网络管理或者在没有图形 ...
- stm32 中断处理函数注意事项
一 前记 最近在公司的一个项目中碰到一个解决了定位很久的 bug , bug 找到的时候发现犯了很低级的错误--在中断处理函数中调用了 printf 函数,因为中断处理函数的调用了不可重入函数,导致接 ...