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

C++自旋锁

一、 什么是自旋锁?

核心定义:

自旋锁是一种非阻塞锁。当线程尝试获取锁失败时,它不会挂起(阻塞/让出 CPU),而是会在一个死循环中持续检查(忙等待 / Busy-Waiting)锁是否被释放。

直观隐喻

  • 互斥锁 (Mutex):你去洗手间,发现门锁了。你回到座位上睡觉。等里面的人出来了,管理员把你叫醒,你再去上。
    • 开销:睡觉(切换上下文)和被叫醒(调度)很累。
  • 自旋锁 (Spinlock):你去洗手间,发现门锁了。你站在门口,每隔 0.1 秒就敲门问:“好了没?好了没?”,直到里面的人出来。
    • 开销:你一直站着(占用 CPU),哪里也去不了。但是一旦门开了,你零延迟冲进去。

二、 为什么需要自旋锁?(底层视角)

您可能会问:“让线程空转浪费 CPU,这不是很傻吗?”

要理解它的价值,必须看**上下文切换(Context Switch)**的成本。

  1. Mutex 的成本
    • std::mutex拿不到锁时,线程会陷入内核态(Kernel Mode)。
    • OS 需要保存当前线程的寄存器、栈指针,刷新 TLB(页表缓存),然后调度另一个线程。
    • 这个过程大约需要3 ~ 10 微秒(在现代 CPU 上)。
  1. Spinlock 的优势
    • 如果您的临界区代码执行时间极短(比如只是做一个pNext = node;的链表操作),耗时可能只有0.01 微秒
    • 为了等待 0.01 微秒的任务,去花费 5 微秒切换线程,是亏本生意
    • 自旋锁全程在**用户态(User Mode)**运行,完全没有系统调用开销。

结论:自旋锁适用于**“锁持有时间极短”**的场景。


三、 C++ 中的自旋锁实现

C++ 标准库并没有直接提供std::spinlock(C++20 只有std::atomic_flag),我们需要利用原子操作自己实现。

1. 最基础的实现:std::atomic_flag

这是 C++ 中唯一保证**无锁(Lock-Free)**的数据类型。

#include <atomic> #include <thread> #include <vector> #include <iostream> class SpinLock { private: // atomic_flag 只有两个状态:set (true) 和 clear (false) // ATOMIC_FLAG_INIT 初始化为 false std::atomic_flag flag = ATOMIC_FLAG_INIT; public: void lock() { // test_and_set(): // 1. 读取当前值 // 2. 将值设为 true // 3. 返回旧值 // 这是一个原子操作 (RMW: Read-Modify-Write) // 如果返回 true,说明之前已经是 true (被别人锁了),则一直循环 (自旋) // memory_order_acquire: 保证获得锁之后的读写操作不会重排到加锁之前 while (flag.test_and_set(std::memory_order_acquire)) { // 这里是自旋区 (Spinning) // 可以在这里加 "CPU pause" 指令优化(后面会讲) } } void unlock() { // 清除标志,设为 false // memory_order_release: 保证解锁之前的读写操作全部完成 flag.clear(std::memory_order_release); } }; // 使用示例(配合 lock_guard 满足 RAII) SpinLock sl; void worker() { // std::lock_guard 需要类满足 BasicLockable (有 lock/unlock 方法) std::lock_guard<SpinLock> guard(sl); // 临界区... }
2. 通用实现:std::atomic<bool>

功能类似,但atomic<bool>可以提供更多 API(比如load查看状态),只是在极老的硬件上可能不是 Lock-Free 的(虽然现在几乎都是)。

C++

class SpinLockBool { std::atomic<bool> locked{false}; public: void lock() { bool expected = false; // CAS (Compare And Swap) // 尝试把 locked 从 false 改成 true // 如果 locked 是 true (被锁),compare_exchange_weak 返回 false,继续循环 while (!locked.compare_exchange_weak(expected, true, std::memory_order_acquire)) { expected = false; // CAS 失败后 expected 会被改成当前值(true),重置为 false 再次尝试 } } void unlock() { locked.store(false, std::memory_order_release); } };

四、 致命陷阱与性能优化(C++ 高阶)

在实现高性能组件(如内存池)时,直接用while(flag.test_and_set())会带来严重的性能问题。

1. 缓存一致性风暴 (Cache Coherence Storm / Bus Contention)
  • 现象:多个线程在一个原子变量上疯狂CAS(写操作)。
  • 原理:根据 CPU 的 MESI 协议,当一个核修改原子变量时,必须让其他核的 Cache Line 失效。如果 10 个线程同时自旋,锁变量所在的 Cache Line 会在 CPU 核心之间疯狂“跳来跳去”,导致总线流量爆炸,甚至拖慢其他不相关线程的速度。
  • 解决Test-Test-and-Set (TTAS)模式。
    • 先用load(读) 检查是否被释放(读操作不独占 Cache Line)。
    • 只有读到false时,才尝试CAS(写)。
2. CPU 流水线空转
  • 现象while循环是一个极紧密的指令序列,CPU 流水线会全速运行,产生大量热量并消耗电力。
  • 解决:CPU Pause 指令。

在 x86 架构下,使用 _mm_pause() 指令(SSE2 扩展)。

    1. 它告诉 CPU “我在自旋”,让 CPU 稍微降低流水线派发速度,节能降温。
    2. 它可以避免退出循环时的内存顺序冲突惩罚。

优化后的 C++ 代码:

#include <atomic> #include <immintrin.h> // for _mm_pause class OptimizedSpinLock { std::atomic_flag flag = ATOMIC_FLAG_INIT; public: void lock() { while (flag.test_and_set(std::memory_order_acquire)) { // 在自旋期间... while (flag.test(std::memory_order_relaxed)) { // 先只读 (Test) // 告诉 CPU 稍微休息一下,不要全速空转 #if defined(__x86_64__) || defined(_M_X64) _mm_pause(); #endif // 如果是 ARM 架构,可以用 __yield() 或 asm("yield") } } } void unlock() { flag.clear(std::memory_order_release); } };
3. 优先级反转与死锁
  • 场景:如果一个低优先级线程拿到了自旋锁,但因为被 OS 调度走了(时间片到了),一个高优先级线程被调度进来,尝试获取同一个自旋锁。
  • 结果:高优先级线程因为是自旋(忙等),它不让出 CPU,导致低优先级线程永远得不到 CPU 来执行解锁操作。于是死锁
  • 对策:在自旋锁中,如果自旋超过一定次数,必须使用std::this_thread::yield()主动让出时间片。
http://www.jsqmd.com/news/74299/

相关文章:

  • 2025 AI推理技术突破:RLPR-Qwen2.5-7B-Base如何终结大模型“验证器依赖“困局?
  • Wan2.2-T2V-A14B在数字孪生城市项目中的动态场景填充应用
  • 思考与练习(大学计算机基础系列:冯·诺依曼模型与 PC 硬件系统)
  • 思考与练习(大学计算机基础系列:操作系统理论与实践)
  • Wan2.2-T2V-A14B在跨文化广告本地化中的语言适应性
  • NCMconverter终极指南:轻松解锁网易云音乐加密文件
  • NCM音乐解锁指南:5分钟掌握加密格式转换技巧
  • Opera GX(游戏浏览器)
  • 掌握这3种R语言插值法,轻松应对复杂环境监测场景
  • 基于springboot的中小学生英语学习阅读系统
  • 2025企业微信私有化部署服务商:微盛AI·企微管家的安全与效率实践
  • Wan2.2-T2V-A14B生成结果可解释性研究进展通报
  • DAY22 推断聚类后簇的类型
  • Wan2.2-T2V-A14B与DALL·E 3联合使用构建图文视频流水线
  • Wan2.2-T2V-A14B模型参与AI电影短片创作的全流程记录
  • Wan2.2-T2V-A14B在AI辅助教学视频个性化生成中的潜力
  • Windows右键菜单终极清理指南:ContextMenuManager让你的电脑焕然一新
  • 5步轻松掌握Windows右键菜单管理:ContextMenuManager终极指南
  • 基于单片机窗帘控制(温湿度,烟雾,红外,光照)系统Proteus仿真(含全部资料)
  • 基于单片机存储柜(4个柜子,LCD1602,矩阵键盘)系统Proteus仿真(含全部资料)
  • 【BUUCTF系列】[强网杯 2019]随便注
  • Wan2.2-T2V-A14B支持多语言文本理解,全球化创作新利器
  • 解锁Wan2.2-T2V-A14B隐藏功能:高级提示词工程技巧
  • 【边缘Agent部署终极指南】:Docker轻量级实战技巧全揭秘
  • (甲基化研究必备技能)R语言实现CpG位点注释与功能富集分析全流程解析
  • N皇后问题
  • 谷城县这家家电门店,竟藏着最全产品,你去过吗?
  • NVIDIA显卡性能调校深度探索:解锁隐藏配置的艺术
  • 如何利用Wan2.2-T2V-A14B生成时序连贯的长视频片段?
  • 11、MobX实战应用与特殊API解析