当前位置: 首页 > news >正文

【多线程】什么是原子操作(Atomic Operation)?

【多线程】什么是原子操作(Atomic Operation)?

本文来自于我关于多线程系列文章。欢迎阅读、点评与交流
1.【多线程】互斥锁(Mutex)是什么?
2.【多线程】临界区(Critical Section)是什么?
3.【多线程】计算机领域中的各种锁
4.【多线程】信号量(Semaphore)是什么?
5.【多线程】信号量(Semaphore)常见的应用场景
6.【多线程】条件变量(Condition Variable)是什么?
7.【多线程】监视器(Monitor)是什么?
8.【多线程】什么是原子操作(Atomic Operation)?

原子操作(Atomic Operation) 是一个非常核心的计算机概念,涉及到并发编程的底层基础。我们来详细拆解一下。

1. 原子操作是什么?

简单来说,原子操作是一个不可分割的操作。在执行过程中,它要么完全执行成功,要么完全没执行,不会出现执行到一半被中断的情况,外界也看不到中间状态。

你可以把它想象成一个“瞬间完成”的操作。

一个经典的非原子操作例子: i++
这个看似简单的语句,在CPU层面通常需要三个步骤:

  1. 读取:从内存中读取变量 i 的值到寄存器。
  2. 修改:在寄存器中将值加一。
  3. 写入:将新值写回内存。

在单线程环境下,这没问题。但在多线程或多核环境下,问题就来了:

  • 线程 A 读取 i 的值为 10。
  • 线程 A 将值加一,变成 11(还在寄存器中,未写回)。
  • 此时,线程 B 被调度,它读取 i 的值,仍然是 10
  • 线程 B 将值加一,变成 11(寄存器中)。
  • 线程 B 将 11 写回内存。
  • 线程 A 被重新调度,也将 11 写回内存。

最终结果 i 等于 11,但两个线程各执行了一次 i++,我们期望的结果应该是 12。这就是因为 i++ 不是原子操作,导致了数据竞争

原子操作的例子: 使用原子加操作。
如果 fetch_add 是一个原子操作,那么整个“读取-修改-写入”序列会被捆绑成一个不可分割的单元。

  • 线程 A 开始执行原子加操作。它会“锁定”这个变量(注意,这里的锁定是硬件层面的机制,不是软件互斥锁)。
  • 在线程 A 完成整个操作(读取、加一、写入)之前,线程 B 无法访问或修改这个变量。
  • 线程 A 完成后,i 变为 11。
  • 线程 B 开始执行原子加操作,读取到的值是 11,加一后变为 12 并写回。

最终结果 i 等于 12,符合预期。

原子操作的核心特性:

  • 不可分割性:操作要么全做,要么不做。
  • 顺序性:原子操作本身是内存屏障的一种,能保证指令执行顺序,避免编译器或CPU的指令重排优化带来问题。

2. 底层是如何实现的?

原子操作的实现不依赖于操作系统或编程语言提供的锁(如 mutex),因为锁本身的实现又需要原子操作。这是一个“先有鸡还是先有蛋”的问题。原子操作的基石是硬件支持,主要是CPU提供的指令。

实现方式可以分为两大类:

方式一:单核处理器

在单核CPU上,实现原子操作相对简单。因为同一时刻只有一个线程在执行(宏观并行,微观串行)。只需要保证在执行原子操作的指令序列期间不被中断即可。

  • 实现方法:在执行关键的几条指令之前关闭中断,执行完毕后再打开中断。这样,操作系统就无法通过时钟中断来剥夺当前CPU的执行权,从而不会发生线程/进程切换,保证了操作的原子性。
方式二:多核处理器

这是现代计算机的常态,也是复杂性的来源。关闭一个核心的中断无法阻止其他核心同时访问同一块内存。

核心机制:基于总线锁和缓存一致性协议(MESI)。

1. 总线锁(Bus Locking) - 古老而有效的方法

  • 原理:CPU在执行原子操作时,会发出一个 LOCK# 信号。这个信号会锁住整个内存总线(或后来的缓存总线)。
  • 效果:在锁住总线期间,其他所有处理器(核心)都无法通过总线访问内存。这就强制实现了原子性,因为只有当前处理器能访问内存。
  • 缺点开销巨大。它阻止了所有其他处理器访问任何内存,即使它们访问的是无关的内存地址,严重影响了系统性能。这是一种“伤及无辜”的粗粒度锁。

2. 缓存锁(Cache Locking) - 现代主流方法

  • 基础:缓存一致性协议(MESI)
    现代CPU每个核心都有自己的高速缓存。为了保持多个缓存中数据的一致性,CPU实现了MESI这样的协议。MESI代表了缓存行的四种状态:

    • M(Modified,修改):缓存行是脏的,与主内存不一致,只存在于当前缓存中。
    • E(Exclusive,独占):缓存行是干净的,与主内存一致,只存在于当前缓存中。
    • S(Shared,共享):缓存行是干净的,与主内存一致,可能存在于多个缓存中。
    • I(Invalid,无效):缓存行中的数据是无效的。
  • 原理:当某个CPU核心要对一个变量进行原子操作时:

    • 如果该变量所在的缓存行处于 E(独占)M(修改) 状态,说明当前核心是唯一持有该缓存行最新数据的核心。
    • 此时,CPU核心可以直接在它的缓存上执行原子操作(如CMPXCHG,比较并交换),而无需声明总线锁。操作完成后,缓存行变为M状态。
    • 如果变量处于 S(共享) 状态,说明其他核心也可能有这份数据的副本。
    • 当前核心会通过缓存一致性协议,向所有其他核心发送一个 “RFO(Request For Ownership)” 请求。
    • 这个请求会强制其他核心将它们对应的缓存行标记为 I(无效)
    • 当当前核心收到所有响应后,它就获得了对该缓存行的独占权,然后就可以安全地执行原子操作了。

    这个过程,从效果上看,相当于“锁定”了特定内存地址所在的缓存行,而不是整个总线。粒度更细,性能更高。

具体的原子指令:
CPU提供了一些直接支持原子操作的机器指令,最常见的包括:

  • CAS(Compare-And-Swap,比较并交换):这是实现各种高级同步原语(如锁、无锁数据结构)的基石。它会先比较某个内存位置的值是否与预期值相等,如果相等,则存入新值。
  • LL/SC(Load-Link / Store-Conditional,链接加载/条件存储):这是一对指令。LL 从内存加载值,SC 会尝试向同一地址写入新值,但只有在从 LLSC 期间该地址未被其他写入修改过时,写入才会成功。

编程语言(如C++的 std::atomic,Java的 java.util.concurrent.atomic 包)和操作系统API最终都会将这些原子指令封装成易用的函数供开发者调用。

总结

特性原子操作互斥锁(Mutex)
实现层级硬件指令(CPU)操作系统内核(软件)
粒度很细,针对单个内存地址较粗,保护一个临界区代码
开销非常低,通常由缓存一致性协议保证较高,需要陷入内核态,可能引起线程挂起和调度
应用场景简单的计数器、标志位、无锁数据结构保护复杂的逻辑代码块或数据结构
关系是实现互斥锁的基础在原子操作(如CAS)之上实现

简单来说,原子操作是CPU提供的一种硬件能力,通过总线锁或更高效的缓存锁(基于MESI协议)来保证对单一内存单元的“读-改-写”操作不可分割。 它是构建一切高级并发控制工具的基石。

http://www.dtcms.com/a/418465.html

相关文章:

  • Visual Studio Code 的 AI 插件汇总
  • Java学习笔记六(集合)
  • 简易分析慢 SQL 的流程和方法
  • Docker 中删除镜像与容器的完整指南
  • 通州手机网站建设北京网站设计实力乐云践新
  • 高速PCB板DDR5数据信号的长STUB要背钻吗?
  • FPGA强化-简易频率计
  • VBScript自动化打印:智能过滤Word文档
  • 解码数据结构内核链表
  • 郑州设计网站公司东莞建设银行电话号码
  • 个人网站的建设目标绥德网站建设
  • Elasticsearch面试精讲 Day 23:安全认证与权限控制
  • 学习嵌入式的第四十三天——ARM——I2C
  • 玳瑁的嵌入式日记---0928(ARM--UART)
  • CentOS 8 部署 Zabbix 7.0 LTS 完整流程(PostgreSQL)及不同系统agent安装
  • 网站开发环境设计怎么在百度做网站推广
  • Langchain+Neo4j+Agent 的结合案例-电商销售
  • 计算机网络【第二章-物理层】
  • 计算机网络【第一章-计算机网络体系结构】
  • mac 安装npm之后,其他终端无法访问
  • 购物网站哪个最好记账公司如何拉客户
  • 汽车网络安全 CyberSecurity ISO/SAE 21434 测试之四
  • ARM芯片架构之APB,ATB总线
  • 死信队列(Dead-Letter Queue,DLQ)
  • Python 2025:网络安全与智能防御新范式
  • 2025软考甄选范文“论分布式事务及其解决方案”,软考高级,系统架构设计师论文
  • 学习前端开发的网站占酷设计网站官网入口
  • 【R语言验证统计量的渐进分布】
  • starrocks查询伪代码
  • R语言中的S3 泛型与方法