理解原子变量之二:从volatile到内存序-进一步的认识
目录
实例1
实例2
实例3
内存序中两个最重要的概念
补记
结论
实例1
看下面的例子:在vs2013中建立如下工程:
#include <thread>
#include <iostream>
#include <chrono>bool done = false;void worker(){std::this_thread::sleep_for(std::chrono::seconds(3));done = true;
}int main(){std::thread workerThread(worker);while (!done){//std::cout << "waiting" << std::endl;不知为什么,加上这句打印就可以跳出循环}std::cout << "thread finished" << std::endl;workerThread.join();std::cin.get();return 0;
}
使用release模式编译该工程,且优化采用O2
结果是程序卡住了,本来3秒钟就可以跳出while(!done)死循环,现在一直停着。
vs2013 release 模式下多线程卡住演示
实例2
实例1为什么会卡住呢?因为编译器在打开优化的情况下,“武断”的认为done永远都是false。编译器意识不到子线程也在改变done的取值,所以就认为done是常量,而不会在运行到while()时读取done的实际值,于是while(!done)就变成了while(true)。假如采用debug模式编译,编译器不会“自作聪明”,程序在运行时将老老实实的不断读取done的实时值,直到done==true为止。所以,debug模式不会卡住,而release模式会卡住(除非在编译时禁止优化)。
现在稍微修改一下代码,用volatile修饰 bool done。即使在release下编译,程序仍然正常走完,不卡住:

这是因为,volatile在提示编译器,done是一个不能被优化掉的变量。即使在release模式下,每次走到while时,也要查看done的值。
回到原子变量的话题上来,再看第三个例子:
实例3
现在用atomic<bool>代替volatile bool。
#include <thread>
#include <iostream>
#include <chrono>
#include <atomic>std::atomic<bool> done(false);void worker() {std::this_thread::sleep_for(std::chrono::seconds(3));done.store(true);
}int main() {std::thread workerThread(worker);while (!done.load()) {//std::cout << "waiting" << std::endl;不知为什么,加上这句打印就可以跳出循环}std::cout << "thread finished" << std::endl;workerThread.join();std::cin.get();return 0;
}
结果与使用volatile一样,while(!done.load())不会一直阻塞程序向下运行:

看到了上面的三个例子,接下来就要请出原子变量里面一个非常重要的概念:内存序。
内存序中两个最重要的概念
std::memory_order - cppreference.com列出了C++11标准里的六个内存序(memory_order)。这6个内存序的作用将在下一篇文章描述。其中多次出现一个词汇“可见”(visible)

以MEMORY_ORDER_ACQUIRE为例,其中文解释是:
有此内存定序的加载操作,在其影响的内存位置进行获得操作:当前线程中读或写不能被重排到此加载之前。其他线程的所有释放同一原子变量的写入,能为当前线程所见
“其他线程的所有释放同一原子变量的写入,能为当前线程所见”这句话的意思是什么?我们只要理解了什么是不可见,就理解了可见。对照实例1,在不使用volatile的情况下,子线程workerThread对done的修改,没有影响到主线程的while(!done)逻辑,这就是说,子线程对done的写入,不为主线程可见。
反观实例2和3,子线程修改了done,主线程的运行随之受了影响,这就是可见。
内存序里有两个重要的概念,一是指令排序,在下一篇里面将介绍;另一个就是可见性(visibility)。从例子2和3可见,在可见性这方面,原子变量的作用与volatile是一样的:原子变量被一个线程改变后,另一个线程肯定会发现这个变化。
补记
实例1,2,3都是在vs2013下演示的。在vs2019下,实例1效果有区别:vs2019的while循环在示例1中同样不会阻塞。这是因为,不同的编译器遵循不同的c++标准。即使遵循相同的C++标准,不同的编译器对同一个标准的贯彻程度也不一样:有的编译器严格遵循标准,既不多做,也不少做;有的编译器在某些次要方面可能达不到C++标准的要求(少做);还有的编译器为了安全性等方面考虑,甚至超过了C++标准的要求(多做)。vs2019很可能属于超过标准这一种:即使是多线程编程中使用普通的非原子变量,非易失(volatile)变量,仍能保证可见性(个人猜测)。
但无论如何,本文探讨的是C++11标准的内存序,不针对具体编译器。在遵守C++11标准方面,VS2013似乎比2019更贴切,所以三个实例均采用2013示范。
结论
前文解释了原子变量的第一个功能:它相当于粒度很细的互斥锁。本文介绍了原子变量的第二个功能:可见性。原子变量保证了多线程编程中的可见性。事实上,原子变量共负担了三个功能。下一篇文章将介绍其第三个功能--限制指令重排。
相关文章:
理解原子变量之二:从volatile到内存序-进一步的认识
目录 实例1 实例2 实例3 内存序中两个最重要的概念 补记 结论 实例1 看下面的例子:在vs2013中建立如下工程: #include <thread> #include <iostream> #include <chrono>bool done false;void worker(){std::this_thread::sle…...
DICOM标准:MR图像模块属性详解——磁共振成像(MR)在DICOM中的应用
目录 引言 磁共振成像(MR) 一、MR图像模块 二、MR图像属性描述 1、图像类型 (Image Type) 2、抽样每个象素 (Sampling per Pixel) 3、光度插值 (Photometric Interpretation) 4、位分配 (Bits Allocated) 结论 引言 数字成像和通信在医学(…...
Linux内核与用户空间
Linux内核与用户空间是Linux操作系统中的两个重要概念,它们各自承担着不同的功能和职责,并通过特定的机制进行交互。以下是对Linux内核与用户空间的详细解释: 一、Linux内核 定义:Linux内核是Linux操作系统的核心组件,…...
计算机网络-以太网小结
前导码与帧开始分界符有什么区别? 前导码--解决帧同步/时钟同步问题 帧开始分界符-解决帧对界问题 集线器 集线器通过双绞线连接终端, 学校机房的里面就有集线器 这种方式仍然属于共享式以太网, 传播方式依然是广播 网桥: 工作特点: 1.如果转发表中存在数据接收方的端口信息…...
找树根和孩子c++
题目描述 给定一棵树,输出树的根root,孩子最多的结点max以及他的孩子 输入 第一行:n(0<结点数<100),m(0<边数<200)。 以下m行;每行两个结点x和y…...
植物源UDP-糖基转移酶及其分子改造-文献精读75
植物源UDP-糖基转移酶及其分子改造 摘要 糖基化能够增加化合物的结构多样性,有效改善水溶性、药理活性和生物利用度,对植物天然产物的药物开发至关重要。UDP-糖基转移酶(UGTs)能够催化糖基从活化的核苷酸糖供体转移到受体形成糖苷键,植物中天然产物的糖基化修饰主要通过UGTs实…...
Redis中String 的底层实现是什么?
Redis中String 的底层实现是什么? Redis 是基于 C 语言编写的,但 Redis 的 String 类型的底层实现并不是 C 语言中的字符串(即以空字符 \0 结尾的字符数组),而是自己编写了 SDS(Simple Dynamic String&…...
像mysql一样查询es
先简单介绍一下这个sql查询,因为社区一直反馈这个Query DSL 实在是太难用了。大家可以感受一下下面这个es的查询。 GET /my_index/_search { “query”: { “bool”: { “must”: [ { “match”: { “title”: “search” } }, { “bool”: { “should”: [ { “te…...
SpringBoot中@Validated或@Valid注解校验的使用
文章目录 SpringBoot中Validated或Valid注解校验的使用1. 添加依赖2. 使用示例准备2-1 测试示例用到的类2-2 实体Dto,加入校验注解2-2 Controller 3. 示例测试4. Valid 和 Validated注解详解4-1 常用规则注解4-2 分组验证4-2-1 示例准备4-2-2 Controller接口4-2-3 P…...
HashMap为什么线程不安全?
一、Put操作(数据覆盖) HashMap底层是基于数组 链表(在 Java 8 以后,当链表长度超过一定阈值时会转换为红黑树)的数据结构。在多线程环境下,当多个线程同时对HashMap进行put操作时,可下面这种…...
类加载器及反射
目录 1.类加载器 1.1类加载【理解】 1.2类加载器【理解】 1.2.1类加载器的作用 1.2.2JVM的类加载机制 1.2.3Java中的内置类加载器 1.2.4ClassLoader 中的两个方法 2.反射 2.1反射的概述【理解】 2.2获取Class类对象的三种方式【应用】 2.2.1三种方式分类 2.2.2示例…...
aws boto3 下载文件
起因:有下载 aws s3 需求,但只有web 登录账号,有 id 用户名 密码,没有 boto3 的 key ID 经过分析,发现网页版有个地址会返回临时 keyID,playwright 模拟登录,用 page.on 监测返回数据ÿ…...
3DDFA-V3——基于人脸分割几何信息指导下的三维人脸重建
1. 研究背景 从二维图像中重建三维人脸是计算机视觉研究的一项关键任务。在虚拟现实、医疗美容、计算机生成图像等领域中,研究人员通常依赖三维可变形模型(3DMM)进行人脸重建,以定位面部特征和捕捉表情。然而,现有的方…...
求串长(不使用任何字符串库函数)
问题描述 编写一个程序,输入一个字符串,输出串的长度。 要求: (1)字符串长度不超过100个字符。 (2)不使用任何字符串库函数,建议使用堆串存储结构。 输入描述 输入一个字符串。 …...
第02章 MySQL环境搭建
一、MySQL的卸载 如果安装mysql时出现问题,则需要将mysql卸载干净再重新安装。如果卸载不干净,仍然会报错安装不成功。 步骤1:停止MySQL服务 在卸载之前,先停止MySQL8.0的服务。按键盘上的“Ctrl Alt Delete”组合键࿰…...
linux系统编程 man查看manual.stat
获取文件属性,(从inode结构体中获取) stat/lstat 函数 int stat(const char *path, struct stat *buf); 参数: path: 文件路径 buf:(传出参数) 存放文件属性,inode结构体…...
从网络到缓存:在Android中高效管理图片加载
文章目录 在Android应用中实现图片缓存和下载项目结构使用 代码解析关键功能解析1. 图片加载方法2. 下载图片3. 保存图片到缓存4. 文件名提取 总结 首先我们需要在配置AndroidManifest.xml里面添加 <uses-permission android:name"android.permission.INTERNET" …...
【数据结构】链表详解:数据节点的链接原理
链表(Linked List)是一种基础的数据结构,是程序设计中用来存储数据的典型方法之一。链表特别适合插入和删除操作频繁的场景,是了解数据结构和算法的基础。本文将从零开始,带大家了解链表的底层原理、类型(单…...
使用AWS Redshift从AWS MSK中读取数据
Amazon Redshift 流式摄取的目的是简化将流式数据直接从流式服务摄取到 Amazon Redshift 或 Amazon Redshift Serverless 的过程。 官方文档[1]中有详细步骤。用unauthenticated, IAM 的方式均可以进行连接,只不过使用的是不同端口:9092或者9098 [1] h…...
从0开始学统计-数据类别与测量层次
数据分析前,我们首先要弄清楚数据的分类。数据并不仅仅是一堆数字和文字,它们实际上代表了我们看待事物属性的不同视角。从最宽泛的角度出发,我们可以将数据划分为定量(比如用数字表示)或者定性(例如&#…...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...
376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...
C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
