当前位置: 首页 > 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.cnnetsun.cn/news/11063.html

相关文章:

  • Windows右键菜单终极清理指南:ContextMenuManager让你的电脑焕然一新
  • 通义千问凭借“门控注意力”斩获 NeurIPS 最佳论文奖!详解Gated Attention原理
  • 11、MobX实战应用与特殊API解析
  • 从加密到解析仅需2分钟:Dify自动化PDF处理黑科技曝光
  • 申请TELEC认证需要准备哪些材料?
  • 大模型微调技术全解析:程序员必备收藏指南
  • ABB AF460-30-11 空气断路器
  • 利用cubemx和HAL库重写江科协的oled驱动程序
  • LAMMPS分子动力学模拟技术与应用
  • 深度学习中 z-score 标准化理解
  • 硬件测试面试题 - 模拟面试|AI面试官
  • QCoreApplication::aboutToQuit 可以链接多个槽函数吗
  • VLAN实验
  • Wan2.2-T2V-A14B模型对不同肤色人种的表现公平性评估
  • CTF竞赛系统、知识竞赛系统、漏洞靶场练习系统
  • 云主机Agent权限失控?AZ-500安全专家教你3招紧急封堵
  • HBase vs. 传统数据库:大数据时代的存储革命
  • Flutter 从原理到实战:深入理解跨平台框架核心与高效开发实践
  • Wan2.2-T2V-A14B实现物理级动态模拟的三大核心技术
  • 再谈ST表
  • 2026年机器人感知与智能控制国际学术会议(RPIC 2026)
  • Wan2.2-T2V-A14B生成视频可用于YouTube盈利吗?合规性解读
  • 【Docker Scout AI漏洞扫描揭秘】:如何利用人工智能精准发现容器安全盲点
  • Spring Kafka 动态消费实现案例
  • Wan2.2-T2V-A14B模型推理性能调优实战技巧分享
  • GraniStudio零代码平台调试算子方式有多少种?分别都是如何调试?
  • 小米14C刷国际版步骤
  • 智谱开源天团登陆 AtomGit,4 大模型覆盖多模态全场景!
  • 开源视频生成技术再突破:Wan2.1-FLF2V-14B模型实现720P高清流畅过渡
  • 教学辅助微信小程序设计毕业设计(源码+lw+部署文档+讲解等)