news 2026/4/16 16:02:55

C++自旋锁

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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()主动让出时间片。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 9:02:04

Opera GX(游戏浏览器)

链接&#xff1a;https://pan.quark.cn/s/9b9669794af5Opera GX是Opera推出的全球首款游戏浏览器&#xff0c;是一款为游戏而生的网页浏览器&#xff0c;可以在网络游戏方面获得更好的体验&#xff0c;喜欢的朋友不要错过哦&#xff01;软件特色第一个游戏浏览器 Opera GX浏览器…

作者头像 李华
网站建设 2026/4/16 12:15:53

【边缘Agent部署终极指南】:Docker轻量级实战技巧全揭秘

第一章&#xff1a;边缘Agent与Docker轻量部署概览 在物联网与边缘计算快速发展的背景下&#xff0c;边缘Agent作为连接终端设备与云端服务的核心组件&#xff0c;承担着数据采集、本地决策和协议转换等关键任务。为了提升部署灵活性并降低资源开销&#xff0c;基于Docker的轻量…

作者头像 李华
网站建设 2026/4/16 10:40:14

Gemma 3 12B It GGUF:Google量化模型本地部署全解析与应用指南

引言&#xff1a;本地化AI的新选择 【免费下载链接】gemma-3-12b-it-GGUF 项目地址: https://ai.gitcode.com/hf_mirrors/unsloth/gemma-3-12b-it-GGUF 在人工智能技术迅猛发展的今天&#xff0c;大型语言模型&#xff08;LLM&#xff09;的应用场景日益广泛。然而&…

作者头像 李华
网站建设 2026/4/15 16:19:24

无线键盘办理TELEC认证办理需要多长时间?

无线键盘&#xff08;常见为蓝牙 / BLE 或 2.4GHz 跳频型&#xff09;的 TELEC 认证&#xff0c;资料齐全且测试一次性通过时&#xff0c;常规周期 3-5 周&#xff1b;若需整改或资料补正&#xff0c;会延长至 5-7 周&#xff0c;加急可压缩至 2-3 周。周期拆解与影响因素常规周…

作者头像 李华
网站建设 2026/4/16 9:03:23

英雄联盟智能助手League Akari:重新定义游戏体验的完整指南

英雄联盟智能助手League Akari&#xff1a;重新定义游戏体验的完整指南 【免费下载链接】LeagueAkari ✨兴趣使然的&#xff0c;功能全面的英雄联盟工具集。支持战绩查询、自动秒选等功能。基于 LCU API。 项目地址: https://gitcode.com/gh_mirrors/le/LeagueAkari Lea…

作者头像 李华
网站建设 2026/4/16 10:40:37

前端技术的下一次跃迁:从工程化到智能化的全面演进

过去十年&#xff0c;前端技术经历了史无前例的高速演化&#xff1a;从页面切图到组件化框架&#xff0c;从手工构建到高度工程化体系&#xff0c;从简单交互到复杂 Web 应用。如今&#xff0c;我们正站在新一代技术浪潮的门口——前端正在从“工程驱动”迈向“智能驱动”。这一…

作者头像 李华