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

关于__sync_bool_compare_and_swap的使用及在多核多线程下使用时的思考

__sync_bool_compare_and_swap这是一个非常重要的原子操作函数,通常简称为 CAS。它在多线程编程、无锁数据结构、操作系统内核中扮演核心角色。

原型及作用
c
bool __sync_bool_compare_and_swap (type *ptr, type oldval, type newval, ...)

功能:(原子性地(Atomic)执行以下操作)
读取指针 ptr 指向的内存位置的值。
将这个值与期望值 oldval 进行比较。
如果相等,则将 newval 写入到这个内存位置,并返回 true。
如果不相等,则不做任何写操作,并返回 false。

参数:
ptr:指向需要操作的内存地址的指针。
oldval:期望该内存地址当前持有的值。
newval:如果当前值等于 oldval,你想要写入的新值。

返回值:
true:比较成功,并且新值 newval 已经被写入。
false:比较失败,当前值不等于 oldval,内存中的值保持不变。

注意事项

GCC 内置函数:_sync* 系列是 GCC 提供的内置原子操作函数。在现代 C/C++ 中,更推荐使用 C11(<stdatomic.h>)或 C++11()标准库中的原子变量和操作,它们可移植性更好。

内存顺序:像 __sync_bool_compare_and_swap 这样的操作通常附带 “完整的内存屏障”,这意味着在 CAS 操作之前的所有内存读写操作都不会被重排到 CAS 之后,反之亦然。这保证了多线程环境下内存访问顺序的一致性。

平台支持:它需要硬件(CPU)的支持。常见的现代处理器架构(x86/x86-64, ARM, PowerPC)都有对应的 CAS 指令


思考的一个问题:__sync_bool_compare_and_swap 多线程环境下,这个接口同一时刻间只能被一个线程调用吗

实际上不是只能被一个线程调用。恰恰相反,它被设计为可以在同一时刻被多个线程同时调用,并且这正是它存在的意义。

核心原理:硬件保证的原子性
__sync_bool_compare_and_swap (CAS) 是一个原子操作。这意味着即使多个线程同时对它同一个内存地址发起调用,CPU 也会确保这些操作是串行化的——即一个接一个地执行,不会交错在一起。

每个 CAS 操作在执行时,对内存的“读取-比较-写入”这一系列动作是不可分割的单一指令。硬件(CPU)会通过缓存锁定总线锁定等机制来保证在执行这个操作时,其他核心无法访问相关的内存地址。

多线程同时调用的行为与结果

假设多个线程同时执行 __sync_bool_compare_and_swap(&value, old, new):它们都会成功“调用”这个函数:从软件层面看,所有线程的代码都执行到了这一行。

但是CPU 会安排它们排队执行:硬件会确保这些并发的 CAS 请求不会同时作用于内存,而是由一个核心先执行,完成后另一个再执行,依此类推。只有一个线程会“成功”。

在这些被串行执行的 CAS 操作中,第一个获得执行权的线程会进行检查。如果 value == old,它会将 value 设置为 new 并返回 true。

后续的线程再执行 CAS 时,它们会发现 value 已经被之前的线程修改了(不再等于它们传入的 old 值),因此它们的操作会失败并返回 false。

缓存锁定(Cache Locking) 和 总线锁定(Bus Locking) 是 CPU 用来实现原子操作(如 __sync_bool_compare_and_swap)的两种底层硬件机制。它们确保了在多核环境下,对一个内存位置的“读-改-写”操作看起来是瞬间完成的、不可分割的。

想象一下,你和几位同事需要修改一个共享的在线文档(比如 Google Docs 里的一个数字)。

总线锁定(Bus Locking):这就像是为了修改这个数字,你锁定了整个文档。在你完成修改、保存并解锁之前,其他所有人都无法编辑文档里的任何一个部分,即使他们想改的是完全不同的地方。这非常安全,但效率极低,因为它阻塞了所有其他人。

缓存锁定(Cache Locking):现代的在线文档更智能。它允许你和你同事同时编辑文档的不同部分。当你想要修改那个数字时,系统只会锁定那一行或那个单元格。你的同事仍然可以自由地编辑文档的其他部分。只有当你和其他人同时想修改同一个单元格时,系统才会进行协调,让一个人先完成,另一个人后完成。这高效得多。

在这个比喻中:
共享文档 = 计算机的主内存(RAM)
文档的不同部分 = 内存中的不同地址
你和你的同事 = 不同的 CPU 核心
锁定整个文档 = 总线锁定
只锁定一个单元格 = 缓存锁定


1. 总线锁定 (Bus Locking) - 老式但可靠的方法

这是早期多核处理器的实现方式。当某个 CPU 核心需要执行一个原子操作时,它会通过在系统总线上发出一个 LOCK# 信号来锁定整个内存总线。

如何工作:

CPU 核心在执行原子指令前发出 LOCK# 信号。

这个信号会阻止其他所有核心访问任何内存位置(严格来说,是穿过总线去访问内存)。

该核心独占地、不受干扰地完成它的原子操作(读-改-写)。

操作完成后,解除 LOCK# 信号,其他核心才能继续访问内存。

缺点:
性能杀手:它导致了巨大的性能瓶颈。即使两个核心操作的是毫不相干的内存地址,一旦一个核心锁定了总线,其他所有核心都必须停下来等待,就像交通中的红灯让所有方向的车都停下一样。

可扩展性差:随着 CPU 核心数量的增加,这种锁竞争会严重拖慢整个系统的速度。


2. 缓存锁定 (Cache Locking) - 现代高效的方法

是什么:这是现代处理器采用的、更高效的机制。它利用了 CPU 的 缓存一致性协议(最常见的是 MESI 及其变种)。

如何工作:

要操作的内存数据通常已经存在于执行操作的 CPU 核心的缓存行中。

当该核心要执行原子操作时,它不需要锁定整个总线,而是利用缓存一致性协议来“锁定”特定的缓存行。

核心通过缓存一致性协议,将自身缓存行的状态置为 “独占” 或 “修改” 态。这意味着它是唯一拥有该数据最新副本的核心。

然后它就可以安全地在自己的缓存里执行原子操作。因为其他核心的对应缓存行会通过协议被标记为“无效”,它们如果想读或写这个数据,必须先向这个核心请求最新的数据,从而被迫等待原子操作完成。

操作完成后,核心将数据写回缓存(可能也会写回主内存),并更新缓存行的状态。其他核心的请求随后得到处理。

优点:
高并发:不同核心可以同时原子地操作位于不同缓存行的内存地址,互不干扰。
高性能:操作发生在高速的缓存中,避免了频繁访问慢速的主内存。
可扩展性好:非常适合多核处理器。


CPU 执行这条指令时,会根据情况自动选择使用缓存锁定还是总线锁定:
如果操作的内存地址完全包含在一个缓存行内,CPU 会使用高效的缓存锁定。

如果操作的内存地址跨越了两个缓存行(即缓存行不对齐),CPU 无法仅锁定一个缓存行,它就会退回到老式的、性能较差的总线锁定来保证原子性。

这就是为什么在多线程编程中,强调内存对齐非常重要,它不仅能提高访问效率,还能确保原子操作使用最优的硬件机制

总而言之,缓存锁定和总线锁定是硬件为实现软件层“原子操作”这一抽象概念而提供的两种底层保障机制,其中缓存锁定是现代CPU高效并发的基础。

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

相关文章:

  • 【嵌入式简单外设篇】-433MHz 无线遥控模块
  • 计算机视觉(opencv)实战三十——摄像头实时风格迁移,附多种风格转换
  • 【数据分享】《中国农村统计年鉴》(1985-2024年)全pdf和excel
  • 2025年中国研究生数学建模竞赛“华为杯”C题 围岩裂隙精准识别与三维模型重构完整高质量成品 思路 代码 结果分享!全网首发!
  • [Linux]文件与 fd
  • FFmpeg 深入精讲(二)FFmpeg 初级开发
  • 睡眠脑电技术文章大纲
  • 计算机等级考试Python语言程序设计备考•第二练
  • 【Python】面向对象(一)
  • Jetson 设备监控利器:Jtop 使用方式(安装、性能模式、常用页面)
  • 「数据获取」《商洛统计年鉴》(2001-2024)
  • 链表的探索研究
  • 2025年工程项目管理软件全面测评
  • JAVA算法练习题day17
  • Nacos:服务注册和配置中心
  • Linux 命令行快捷键
  • EasyClick JavaScript Number
  • LeetCode:42.将有序数组转化为二叉搜索树
  • 海外代理IP网站有哪些?高并发场景海外代理IP服务支持平台
  • JavaScript数据交互
  • 11.2.5 自定义聊天室
  • 力扣:字母异味词分组
  • Linux视频学习笔记
  • 2014/12 JLPT听力原文 问题四
  • Elasticsearch面试精讲 Day 21:地理位置搜索与空间查询
  • 华为数字化实战指南:从顶层设计到行业落地的系统方法论
  • 外部 Tomcat 部署详细
  • 【回文数猜想】2022-11-9
  • 216. 组合总和 III
  • Bugku-请攻击这个压缩包