c++ 内存顺序acquire-release c++如何实现自旋锁

千静酱_7862

千静酱_7862

2026-03-22

523人浏览

原创

自旋锁必须用memory_order_acquire和memory_order_release以建立同步点:加锁需看到之前所有写,解锁需确保本线程写对后续加锁者可见;仅用relaxed会导致重排、陈旧数据或写延迟,引发偶发竞争。

c++ 内存顺序acquire-release c++如何实现自旋锁

自旋锁为什么必须用 memory_order_acquire 和 memory_order_release

因为自旋锁的核心不是“锁住变量”,而是建立线程间访问的同步点:加锁要看到之前所有线程的写,解锁要确保自己的写对之后加锁的线程可见。只靠 memory_order_relaxed 会触发重排,导致临界区读到陈旧数据或写被延迟提交。

典型错误是把 compare_exchange_weak 全部设成 memory_order_relaxed,结果在 ARM/AArch64 或优化后的 x86 上出现偶发数据竞争——编译器和 CPU 都可能把临界区外的访存挪进锁内,或把锁内的写拖到锁外。

  • 加锁成功时,要用 memory_order_acquire:它禁止后续读写上移(即保证“进入临界区后看到的都是新值”)
  • 解锁时,要用 memory_order_release:它禁止前面读写下移(即保证“临界区内所有修改在解锁后才对外可见”)
  • 失败重试的 compare_exchange_weak 可以用 memory_order_relaxed,但成功路径必须严格区分

std::atomic_flag 比 std::atomic 更适合实现自旋锁

std::atomic_flag 是 C++ 唯一保证无锁(lock-free)的原子类型,且默认初始化为 clear 状态,语义更贴近“锁”的本意;而 std::atomic 在某些平台可能退化为基于互斥量的模拟实现,反而引入隐藏锁开销。

常见误用是手动用 std::atomic + exchange(true) 实现 try_lock,但 exchange 默认是 memory_order_seq_cst,性能差,且无法精确控制 acquire/release 语义。

  • 用 test_and_set(memory_order_acquire) 实现加锁(注意:首次调用前需 clear(memory_order_relaxed) 初始化)
  • 用 clear(memory_order_release) 实现解锁
  • 不要对 std::atomic_flag 使用 load 或 store —— 它不提供这些接口,强行绕过会破坏语义

自旋锁不能直接替代 std::mutex,尤其在高争用或长临界区场景

自旋锁本质是“忙等”,CPU 一直执行空循环,不交出时间片。当锁被持有时长超过几个微秒,或者多个线程反复争抢,自旋消耗的 CPU 时间远超系统调度开销,反而降低整体吞吐。

C++14
C++14

C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。

下载

典型踩坑是把数据库连接池的临界区、含系统调用(如 read/write)或内存分配(new)的代码塞进自旋锁——这些操作可能阻塞数十毫秒,期间其他核还在白跑。

  • 适合场景:临界区极短(通常
  • 不建议在用户态通用库中暴露裸自旋锁接口;若需,至少加上退避策略(如 std::this_thread::yield() 或指数退避)
  • Clang/GCC 在 -O2 下可能将简单循环优化成 pause 指令,但别依赖——显式调用 _mm_pause() 更可控(需 )

一个最小可行的自旋锁实现要注意什么

不是写个 while(flag.test_and_set()) 就完事。真正能上线的版本必须处理初始化、内存序、退避和可调试性。

错误示例:flag.test_and_set() 没指定内存序,且没初始化;正确做法是用 ATOMIC_FLAG_INIT 或构造函数明确清零,并在加锁失败时插入退避。

  • 初始化必须用 std::atomic_flag flag = ATOMIC_FLAG_INIT 或 C++20 的 std::atomic_flag flag{};
  • 加锁循环里,失败后应调用 std::this_thread::yield(),避免单核全占;更优是用 _mm_pause()(x86)或 __builtin_ia32_pause()(跨平台)
  • 不要在析构函数里自动解锁——自旋锁不管理所有权,解锁必须由业务逻辑显式控制
  • 调试时可加计数器统计自旋次数,但发布版务必删掉,避免干扰内存序和性能

复杂点在于:acquire-release 本身不保证操作原子性,只是约束重排;真正的原子性来自 test_and_set 的底层指令(如 x86 的 xchg)。这两层必须同时成立,缺一不可。

C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!

相关文章

c++速学教程(入门到精通)
c++速学教程(入门到精通)

c++怎么学习?c++怎么入门?c++在哪学?c++怎么学才快?不用担心,这里为大家提供了c++速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

c++

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

2023.10.19

3188

3

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

2025.10.17

5089

12

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2025.12.29

3829

9

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

2026.01.19

466

15

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

2023.08.10

4438

6

数据库三范式
数据库三范式

数据库三范式是一种设计规范,用于规范化关系型数据库中的数据结构,它通过消除冗余数据、提高数据库性能和数据一致性,提供了一种有效的数据库设计方法。本专题提供数据库三范式相关的文章、下载和课程。

2023.06.29

2885

3

如何删除数据库
如何删除数据库

删除数据库是指在MySQL中完全移除一个数据库及其所包含的所有数据和结构,作用包括:1、释放存储空间;2、确保数据的安全性;3、提高数据库的整体性能,加速查询和操作的执行速度。尽管删除数据库具有一些好处,但在执行任何删除操作之前,务必谨慎操作,并备份重要的数据。删除数据库将永久性地删除所有相关数据和结构,无法回滚。

2023.08.14

4041

10

vb怎么连接数据库
vb怎么连接数据库

在VB中,连接数据库通常使用ADO(ActiveX 数据对象)或 DAO(Data Access Objects)这两个技术来实现:1、引入ADO库;2、创建ADO连接对象;3、配置连接字符串;4、打开连接;5、执行SQL语句;6、处理查询结果;7、关闭连接即可。

2023.08.31

3211

3

MySQL恢复数据库
MySQL恢复数据库

MySQL恢复数据库的方法有使用物理备份恢复、使用逻辑备份恢复、使用二进制日志恢复和使用数据库复制进行恢复等。本专题为大家提供MySQL数据库相关的文章、下载、课程内容,供大家免费下载体验。

2023.09.05

1007

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C++ Core Guidelines
C++ Core Guidelines

共0课时 | 0人学习

C++ Reference
C++ Reference

共0课时 | 0人学习

C++ 官方标准说明
C++ 官方标准说明

共0课时 | 0人学习