深入理解 C++ volatile 与 atomic:五大用法解析 + 六大高频考点

一、volatile

volatile是C++中一个非常重要的关键字。volatile关键字告诉编译器,被修饰的变量可能会在程序控制之外被改变,因此编译器不能对该变量的访问进行优化。什么意思呢?现代处理器架构中,有寄存器,L1缓存,L2 缓存,L3 缓存,内存这种架构,可以发现,为了提高访问速度,会将计算的中间变量直接保存在缓存中,再慢慢刷新到内存。

在这里插入图片描述

这么做当然提高了访问速度,但是!但是!但是!数据被修改后是不能直接反馈到内存的,这样做会存在一些问题。

1.1、volatile的基本含义

volatile关键字告诉编译器,被修饰的变量可能会在程序控制之外被改变,因此编译器不能对该变量的访问进行优化。

volatile int flag = 0;
  • 读取可见性:每次读取volatile变量都必须从内存中读取,不能使用寄存器和三级缓存中的缓存值
  • 写入可见性:每次写入volatile变量都必须立即写入内存
  • 顺序性:对volatile变量的操作不会被编译器重排序(但处理器仍可能重排序)

1.2、volatile的主要用途

1.2.1、硬件寄存器访问

在嵌入式系统中,硬件寄存器通常映射到内存地址,其值会由硬件改变:

volatile unsigned int *reg = (unsigned int *)0x1234;
*reg = 1;       // 写入寄存器
int val = *reg; // 必须从寄存器读取,不能使用缓存值

1.2.2、多线程共享变量

在C++11之前,volatile有时被用来实现线程间共享变量(注意:这不是标准推荐的做法):

volatile bool ready = false;// 线程1
void producer() {ready = true; // 告诉消费者数据已准备好
}// 线程2
void consumer() {while(!ready); // 等待数据准备好
}

1.2.3、信号处理程序中的变量

信号处理程序(Signal Handler)是操作系统提供的一种异步事件处理机制,用于响应系统或程序运行时发生的各种事件(称为"信号")。它是Unix/Linux系统编程和C/C++底层编程中的重要概念。

信号是操作系统向进程发送的异步通知,用于通知进程发生了某种事件。常见信号包括:

  • SIGINT (2):终端中断信号(通常是Ctrl+C)
  • SIGSEGV (11):段错误(非法内存访问)
  • SIGTERM (15):终止信号
  • SIGALRM (14):定时器信号
  • SIGUSR1 (10)/SIGUSR2 (12):用户自定义信号

当变量可能在信号处理程序中被修改时:

volatile sig_atomic_t signal_received = 0;void handler(int) {signal_received = 1;
}

在信号处理程序中使用volatile关键字修饰变量是为了解决编译器优化可能导致的可见性问题,确保信号处理程序与主程序之间能够正确通信。

1.2.4、volatile与const的结合

volatile可以和const结合使用,表示变量在程序内不可修改,但可能被外部修改:

const volatile int hardware_clock = 0x1234;

1.3、volatile的局限性

  • 不是线程安全的volatile不提供原子性保证,不能替代std::atomic

  • 不保证内存顺序:不提供内存屏障或顺序一致性

  • 不阻止处理器重排序:仅阻止编译器优化,不限制处理器行为

二、std::atomic

std::atomic是C++11引入的模板类,为多线程编程提供了真正的原子操作支持,解决了volatile在多线程环境中的不足。

2.1、定义

std::atomic提供了一种线程安全的方式来访问和修改共享数据:

#include <atomic>
std::atomic<int> counter(0); // 原子整型变量

2.1.1、基本原子操作

std::atomic<int> val;val.store(42);                                     // 原子存储
int x = val.load();                                // 原子加载
int y = val.exchange(43);                          // 原子交换
bool success = val.compare_exchange_strong(expected = x, desired = 44); // 比较交换,比较原子变量的当前值是否与 expected 相等// 如果相等,则将原子变量的值设置为 desired,并返回 true// 如果不相等,则将 expected 更新为原子变量的当前值,并返回 false

2.1.2、原子算数运算(仅限整型)

std::atomic<int> count(0);count.fetch_add(1);      // 原子加
count.fetch_sub(1);      // 原子减
count++;                 // 等价于fetch_add(1)

2.1.3、基本原子操作(仅限整型)

std::atomic<int> flags(0);flags.fetch_and(0x0F);   // 原子与
flags.fetch_or(0x01);    // 原子或

2.2、内存顺序详解

C++原子操作允许指定内存顺序,控制操作的可见性和顺序性:

enum memory_order {memory_order_relaxed,   // 最宽松,只保证原子性memory_order_consume,   // 数据依赖顺序memory_order_acquire,   // 获取操作memory_order_release,   // 释放操作memory_order_acq_rel,   // 获取-释放操作memory_order_seq_cst    // 顺序一致性(默认)
};

2.2.1、 memory_order_seq_cst (顺序一致性)

  • 最强保证:所有线程看到的内存操作顺序一致
  • 性能开销:最大
  • 默认选择:如果不确定就用这个

举个顺序一致性的例子:

std::atomic<int> x(0), y(0);// 线程1
x.store(1, std::memory_order_seq_cst);  // A
y.store(1, std::memory_order_seq_cst);  // B// 线程2
int r1 = y.load(std::memory_order_seq_cst);  // C
int r2 = x.load(std::memory_order_seq_cst);  // D

可能的执行顺序:

  • A → B → C → D (r1=1, r2=1)
  • A → C → B → D (r1=0, r2=1)
  • C → D → A → B (r1=0, r2=0)

不会出现 r1=1 且 r2=0 的情况,因为这违反顺序一致性。每个seq_cst操作都相当于一个全内存屏障(full memory fence),阻止屏障前后的任何内存操作跨越屏障

2.2.2、memory_order_acquire (获取语义)

  • 适用场景:读操作(加载)
  • 保证:当前操作之后的所有内存访问(包括非原子操作)不会被重排序到它前面
  • 效果:获取其他线程释放的内容

2.2.3、memory_order_release (释放语义)

  • 适用场景:写操作(存储)

  • 保证:当前操作之前的所有写操作不会被重排序到它后面

  • 效果:释放内容给其他获取的线程

举个获取语义与释放语义的例子:

std::atomic<bool> ready(false);
int data = 0;void producer() {data = 42;                                    // (1) 非原子写入ready.store(true, std::memory_order_release); // (2) 原子释放存储
}void consumer() {while (!ready.load(std::memory_order_acquire)) { // (3) 原子获取加载// 忙等待}assert(data == 42);                              // (4) 保证成立
}

2.2.4、memory_order_acq_rel (获取-释放)

  • 适用场景:读-修改-写操作(如fetch_add)
  • 保证:同时具有acquire和release语义,保证操作之后的所有内存访问不会被重排序到它前面,保证操作之前的所有内存访问不会被重排序到它后面

2.2.5、memory_order_consume (消费语义)

  • 类似acquire但更弱:只保证依赖该加载操作的数据不被重排序
  • 较少使用:多数情况下用acquire更安全

2.2.6、memory_order_relaxed (宽松顺序)

  • 最弱保证:只保证原子性,不保证顺序
  • 适用场景:计数器等不需要同步的场景

2.3、std::atomic 的局限性

  • 原子操作不是免费的,比非原子操作慢
  • 复杂的同步问题可能需要结合其他同步机制(如互斥锁)

三、考点

3.1、volatilestd::atomic 的区别

特性volatileatomic
线程安全不保证保证
原子性不保证保证
内存顺序可控制
适用场景硬件寄存器、信号处理多线程同步

volatile仅保证:

  • 编译器不会优化掉对变量的访问
  • 每次访问都从内存读取/写入

但不保证:

  • 操作的原子性
  • 多线程环境下的可见性和顺序性

3.2、什么是原子操作,为什么需要原子操作

原子操作是指在多线程或并发环境中不可分割的操作,它要么完全执行,要么完全不执行,不会被其他线程或进程中断的操作。

原子操作主要解决并发编程中的竞态条件(Race Condition)问题:

  1. 保证操作的完整性:防止操作被其他线程中断导致数据不一致
  2. 避免竞态条件:确保共享资源的正确访问
  3. 实现线程安全:不需要使用锁的情况下保证线程安全
  4. 提高性能:相比锁机制,原子操作通常有更高的性能

3.3、C++11中的 std::atomic 提供了哪些基本原子类型?

C++提供了以下基本原子类型:

std::atomic<bool>
std::atomic<char>
std::atomic<int>
std::atomic<long>
std::atomic<long long>
std::atomic<bool>
std::atomic<char>
std::atomic<unsigned int>
std::atomic<unsigned long>
std::atomic<unsigned long long>

3.4、请用 std::atomic 实现一个简单的自旋锁

class SpinLock {std::atomic<bool> flag{false};
public:void lock() {while(flag.exchange(true, std::memory_order_acquire)) {// 自旋等待}}void unlock() {flag.store(false, std::memory_order_release);}
};

3.5、原子操作和互斥锁(mutex)有什么区别?各自适用什么场景?

特性原子操作互斥锁
实现方式无锁基于锁
阻塞非阻塞可能阻塞
适用范围简单数据类型复杂操作/多变量
性能更高较低
死锁风险
中断安全性安全不安全

适用场景:

  • 原子操作:计数器、标志位、简单状态等
  • 互斥锁:复杂数据结构、需要保护多个变量的操作等

3.6、volatile能否保证操作的原子性?为什么?

不能。volatile只能保证内存可见性(每次读取都从内存获取最新值),但不能保证操作的原子性

例如,volatile int i = 0; i++;这样的操作在多线程环境下仍然是不安全的,因为i++实际上是"读取-修改-写入"三个步骤的组合操作,可能被其他线程中断。

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

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

相关文章

跨主机管理Docker容器化应用的操作与技巧

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; 环境准备与 Docker 安装 在开始跨主机管理 Docker 容器化应用之前&#xff0c;需要确保所有主机上都安装了 Docker 引擎&#xff0c;并且这些主机之间可以通过 SSH 协议进行通信。本节将详细介绍环境准备和 Doc…

编程实践:sigmastar330 调用IVE图像处理加速

说明:本专栏文章有两种解锁方案 1:付费订阅,畅享所有文章 2:免费获取,点击下方链接,关注,自动获取免费链接 https://free-img.400040.xyz/4/2025/04/29/6810a50b7ac8b.jpg 主题:利用IVE进行图像处理加速 Sigmastar 支持的硬件操作,基本都在:mi_ive.h 文件中,本文…

Nginx+PHP+MySQL安装参考

NginxPHPMySQL安装参考 CentOS7环境 配置CentOS7网络&#xff1a; CentOS(最小安装)默认是不打开网络的 启动网络 vi打开&#xff1a;/etc/sysconfig/network-scripts/ifcfg-ens33 文件 将 “ONBOOT:no”属性修改为&#xff1a;“ONBOOT:yes” 重启网络服务 # sudo service …

JavaScript中的反射魔法:揭秘Reflect对象的核心方法(下)

JavaScript中的Reflect对象&#xff1a;高级方法解析&#xff08;下&#xff09; 在JavaScript中&#xff0c;Reflect对象不仅提供了基础的对象操作方法&#xff08;如get、set等&#xff09;&#xff0c;还包含了许多高级API&#xff0c;用于更精细地控制对象行为。本文将继续…

【数字人开发】Unity+百度智能云平台实现长短文本个性化语音生成功能

一、创建自己的应用 百度智能云控制台网址&#xff1a;https://console.bce.baidu.com/ 1、创建应用 2、获取APIKey和SecretKey 3、Api调试 调试网址&#xff1a;https://console.bce.baidu.com/support/?timestamp1750317430400#/api?productAI&project%E8%AF%AD%E9%…

银河麒麟 | ubuntu 搭建属于自己的邮件服务器

目录 遇权不绝就转root 更新系统 安装 Postfix 配置 Postfix 重启 Postfix 安装 Dovecot 配置 Dovecot 编辑 Dovecot 的 IMAP 配置文件 编辑 Dovecot 的用户认证配置文件 编辑 Dovecot 的服务配置文件 重启 Dovecot 安装发送邮箱功能 发送邮件 测试 遇权不绝就转…

嵌入式通信协议框架的四层架构设计与实现

文章目录 一、硬件抽象层&#xff1a;数据收发的基石1.1 设计要点1.2 代码示例 二、协议管理层&#xff1a;智能路由中枢2.1 设计要点2.2 代码示例 三、协议处理层&#xff1a;协议具体实现3.1 设计要求3.2代码示例3.2.1 协议公共定义3.2.2 协议一设计3.2.3 协议二设计 四、应用…

RA信号处理

ra_snr_gui.m 作用&#xff1a;统计不同信噪比下&#xff0c;五种信号的峰值旁瓣比RA和低高频均值比RM&#xff0c;绘制结果&#xff0c;参考图3.11和3.12 DFCW_RA_SNR.m 作用&#xff1a;产生正交离散频率编码信号&#xff0c;并计算峰值旁瓣比RA和低高频均值比 RM LFM_RA_S…

【go的测试】单测之gomock包与gomonkey包

目录 使用gomock包 1. 安装mockgen 2. 定义接口 3. 生成mock文件 4. 在单测中使用mock的函数 5. gomock 包的使用问题 使用gomonkey包 1. mock 一个包函数 2. mock 一个公有成员函数 3. mock 一个私有成员函数 使用gomock包 1. 安装mockgen go get -u github.com/go…

html实现登录与注册功能案例(不写死且只使用js)

目录 案例需求 实现思路 代码参考 login.html register.html 运行效果 升级思路 案例需求 需要一个登录界面和注册页面实现一个较为完整的登录注册功能 1.登录界面没有登录限制需求&#xff08;降低难度&#xff09;&#xff0c;实现基本的登录判断需求&#xff0c;弹窗…

PHP is the best language.

PHP很好写。 众所周知Python很好写&#xff0c;Python 也能开发 Web 应用&#xff0c;但和 PHP 相比&#xff0c;在“直接处理网页”这件事上&#xff0c;PHP 更加贴近底层和原生。 想快速搭建原型或者 B 端后台工具&#xff0c;不妨用 PHP Laravel 来搞&#xff0c;真的很香…

Mybatis-Plus 在 getOne() 的时候要加上 .last(“limit 1“)

1.先写结论: 1.为了确保 SQL 查询只返回一条记录&#xff08;当查询返回多条时会报错->多为代码本身问题&#xff09;。 2.防止数据库执行全表扫描 3.参考网址&#xff1a;问题记录&#xff1a;MyBatis-Plus 中 ServiceImpl 类的 getOne_mybatis_无他&唯手熟尔-2048…

C语言:二分搜索函数

一、二分搜索基本概念 二分搜索&#xff08;Binary Search&#xff09;是一种在有序数组中查找特定元素的高效算法&#xff0c;时间复杂度为O(log n)。 基本特点&#xff1a; 仅适用于有序数组&#xff08;升序或降序&#xff09; 每次比较将搜索范围减半 比线性搜索(O(n))…

[前端AI]LangChain.js 和 Next.js LLM构建——协助博客撰写和总结助手

LangChain.js 和 Next.js LLM 后端应用于协助博客撰写和总结领域是一个非常实用的方向&#xff01;这涉及到理解和处理文本内容&#xff0c;并生成新的、有结构的信息。 根据您之前提供的代码和需求&#xff0c;我们可以在此基础上进行更具针对性的功能规划和技术实现。 博客…

用 GitHub Issues 做任务管理和任务 List,简单好用!

说实话&#xff0c;我平时也是一个人写代码&#xff0c;每次开完会整理任务最麻烦&#xff1a; 一堆事项堆在聊天里、文档里&#xff0c;或者散落在邮件里…… 为了理清这些&#xff0c;我通常会做一份 List&#xff0c;标好优先级&#xff0c;再安排到每日的工作里 虽然这个…

每日算法刷题Day35 6.22:leetcode枚举技巧枚举中间2道题,用时1h

枚举中间 对于三个或者四个变量的问题&#xff0c;枚举中间的变量往往更好算。 为什么&#xff1f;比如问题有三个下标&#xff0c;需要满足 0≤i<j<k<n&#xff0c;对比一下&#xff1a; 枚举 i&#xff0c;后续计算中还需保证 j<k。 枚举 j&#xff0c;那么 i 和…

【教学类-18-06】20250623蒙德里安黑白七款合并WORD(500张、无学号)

背景需要 客户买了蒙德里安黑白格子7种尺寸,但是不需要学号方块,并指定要WORD 设计思路 【教学类-18-05】20241118正方形手工纸(蒙德里安-风格派-红黄蓝黑白)-CSDN博客文章浏览阅读1.3k次,点赞29次,收藏18次。【教学类-18-05】20241118正方形手工纸(蒙德里安-风格派-红…

langchain--(4)

7 Embedding文本向量化 Embedding文本向量化是一种将非结构化文本转化为低维、连续数值向量的技术,旨在通过数学方式捕捉文本的语义、语法或特征信息,从而让机器更高效地处理语言任务。其核心思想源于流形假设(Manifold Hypothesis),即认为高维原始数据(如文本)实际隐含…

DMDRS部署实施手册(ORACLE=》DM)

DMDRS部署实施手册&#xff08;ORACLE》DM&#xff09; 1 同步说明2 DMDRS安装3 数据库准备3.1 源端准备3.1.1 开启归档日志和附加日志3.1.2 关闭回收站3.1.3 创建同步用户 3.2 目标准备3.2.1 创建同步用户 4 DMDRS配置4.1 源端配置4.2 目标配置 5 DMDRS启动5.1 启动源端服务5.…

十(1)作业:sqli-labs重点关卡

参考文章&#xff1a;详细sqli-labs&#xff08;1-65&#xff09;通关讲解-CSDN博客 第1关&#xff1a; 输入 &#xff1a; ?id3 输入 &#xff1a; ?id2 当输入的数字不同&#xff0c;页面的响应也不同&#xff0c;说明&#xff0c;输入的内容被带入到数据库里查询了 输…