news 2026/4/16 19:51:54

【C/C++】自旋锁 Spin Lock

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【C/C++】自旋锁 Spin Lock

自旋锁(Spinlock)详解

什么是自旋锁?

自旋锁是一种轻量级的同步机制。当线程尝试获取锁但锁已被占用时,线程不会进入睡眠状态,而是在原地"自旋"(忙等待),不断检查锁是否可用。

与互斥锁(mutex)的区别在于:互斥锁在获取失败时会让线程睡眠,涉及操作系统调度和上下文切换;自旋锁则保持线程运行,持续尝试获取锁。

适用场景

自旋锁适合以下情况:

临界区非常短,预期锁很快就会被释放。上下文切换的开销比自旋等待更大。在高频交易(HFT)等对延迟极度敏感的系统中尤其常见。

不适合的情况包括:临界区较长、锁竞争激烈、或者线程数远超 CPU 核心数。

基础实现

最简单的自旋锁可以用std::atomic_flag实现:

#include<atomic>classSpinlock{std::atomic_flag flag=ATOMIC_FLAG_INIT;public:voidlock(){while(flag.test_and_set(std::memory_order_acquire));}voidunlock(){flag.clear(std::memory_order_release);}};

test_and_set是一个原子操作,它返回 flag 当前的值,并将 flag 设置为 true。如果返回 false,说明之前没有人持有锁,我们成功获取;如果返回 true,说明锁已被占用,继续自旋。

内存序的选择

为什么用memory_order_acquirememory_order_release

acquire语义确保:在获取锁之后的所有读写操作,不会被重排到获取锁之前。这样我们能看到前一个持有者在临界区内的所有写入。

release语义确保:在释放锁之前的所有读写操作,不会被重排到释放锁之后。这样下一个获取锁的线程能看到我们在临界区内的所有写入。

这两者配合,形成了一个同步点,保证临界区的可见性。

性能问题:缓存行颠簸

上面的基础实现有一个问题。在高竞争情况下:

while(flag.test_and_set(std::memory_order_acquire));

test_and_set每次都会写入 flag,即使锁已经被占用。多个核心同时写同一个缓存行会导致缓存行在核心之间不断传递,这叫做"缓存行颠簸"或"乒乓效应",严重影响性能。

优化:Test-and-Test-and-Set (TTAS)

改进方案是先只读检查,只有当锁看起来可用时才尝试获取:

voidlock(){while(true){// 第一步:只读等待while(flag.test(std::memory_order_relaxed));// 第二步:尝试获取if(!flag.test_and_set(std::memory_order_acquire))return;}}

test是只读操作,不会使其他核心的缓存行失效。多个线程可以同时在本地缓存上自旋,不产生总线流量。只有当锁释放时,缓存行才会更新,线程才会尝试test_and_set

进一步优化:PAUSE 指令

在自旋循环中加入pause指令可以进一步优化:

#include<immintrin.h>voidlock(){while(true){while(flag.test(std::memory_order_relaxed)){_mm_pause();}if(!flag.test_and_set(std::memory_order_acquire))return;}}

_mm_pause是 x86 的 PAUSE 指令,它告诉 CPU “我在自旋等待”。好处包括:降低功耗、减少流水线刷新、在超线程环境下让出资源给兄弟线程。延迟大约 10-40 个时钟周期,具体取决于 CPU 型号。

如果用 GCC 或 Clang,也可以用__builtin_ia32_pause()代替,不需要额外头文件。

跨平台考虑

PAUSE 指令是 x86 特有的。ARM 架构有类似的 YIELD 指令:

#ifdefined(__x86_64__)||defined(_M_X64)_mm_pause();#elifdefined(__aarch64__)asmvolatile("yield");#endif

混合策略

如果锁可能被持有较长时间,可以采用混合策略。先自旋一段时间,如果还没获取到就让出 CPU:

voidlock(){// 先自旋for(inti=0;i<1000;i++){if(!flag.test(std::memory_order_relaxed)){if(!flag.test_and_set(std::memory_order_acquire))return;}_mm_pause();}// 自旋太久,退让while(flag.test_and_set(std::memory_order_acquire)){std::this_thread::yield();}}

yield会让出当前时间片,让操作系统调度其他线程。开销比 pause 大得多(微秒级 vs 纳秒级),但避免了长时间空转。

公平性问题

上面的实现都是不公平的。多个线程竞争时,不保证先来的先获取。在极端情况下可能导致某些线程饥饿。

如果需要公平性,可以实现票据锁(Ticket Lock):

#include<atomic>#include<immintrin.h>classTicketLock{std::atomic<size_t>next_ticket{0};std::atomic<size_t>now_serving{0};public:voidlock(){size_t my_ticket=next_ticket.fetch_add(1,std::memory_order_relaxed);while(now_serving.load(std::memory_order_acquire)!=my_ticket){_mm_pause();}}voidunlock(){now_serving.fetch_add(1,std::memory_order_release);}};

每个线程取一个号码,按号码顺序获取锁。严格先来先服务。

完整代码

最终优化版本:

#include<atomic>#include<immintrin.h>classSpinlock{std::atomic_flag flag=ATOMIC_FLAG_INIT;public:voidlock(){while(true){// 只读自旋,避免缓存行颠簸while(flag.test(std::memory_order_relaxed)){_mm_pause();}// 尝试获取if(!flag.test_and_set(std::memory_order_acquire))return;}}voidunlock(){flag.clear(std::memory_order_release);}};

总结

实现自旋锁需要注意以下几点。使用正确的内存序保证可见性。用 TTAS 模式避免缓存行颠簸。在自旋循环中加入 PAUSE 指令。根据场景选择是否需要公平性。明确自旋锁的适用场景,不要滥用。

在高频交易等延迟敏感场景,自旋锁比互斥锁更合适,因为它避免了上下文切换的开销。但在锁竞争激烈或临界区较长的情况下,互斥锁可能是更好的选择。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 12:27:24

StructBERT中文语义系统:零代码实现批量文本特征提取

StructBERT中文语义系统&#xff1a;零代码实现批量文本特征提取 1. 为什么你需要一个“真正懂中文”的语义工具&#xff1f; 你有没有遇到过这样的情况&#xff1a; 用通用文本向量模型计算两段话的相似度&#xff0c;结果“苹果手机”和“香蕉牛奶”居然有0.68的相似分&am…

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

7个终极方案解决DS4Windows手柄连接的顽固问题

7个终极方案解决DS4Windows手柄连接的顽固问题 【免费下载链接】DS4Windows Like those other ds4tools, but sexier 项目地址: https://gitcode.com/gh_mirrors/ds/DS4Windows 你是否曾在激烈的游戏对战中突然遭遇手柄无响应&#xff1f;或者花费数小时仍无法让DS4Wind…

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

Qwen3-4B文本大模型快速上手:支持中文/英文/日文/法文实时翻译演示

Qwen3-4B文本大模型快速上手&#xff1a;支持中文/英文/日文/法文实时翻译演示 1. 为什么这个“纯文本”模型值得你立刻试试&#xff1f; 你有没有遇到过这样的情况&#xff1a;想快速把一段法语产品说明翻成中文&#xff0c;但翻译工具要么生硬拗口&#xff0c;要么卡在半路…

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

新手必看!HeyGem批量视频生成操作全解析

新手必看&#xff01;HeyGem批量视频生成操作全解析 你是不是也遇到过这样的场景&#xff1a;要给几十个产品拍口播视频&#xff0c;但请真人出镜成本太高、周期太长&#xff1b;用AI数字人又卡在“每次只能做1个”&#xff0c;反复上传、等待、下载&#xff0c;折腾到怀疑人生…

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

手把手教你用Open Interpreter搭建本地AI编程助手

手把手教你用Open Interpreter搭建本地AI编程助手 1. 为什么你需要一个真正属于自己的AI编程助手 你有没有过这样的经历&#xff1a;想快速分析一份Excel数据&#xff0c;却卡在写pandas代码上&#xff1b;想给同事生成一份带图表的周报&#xff0c;结果调试matplotlib花了半…

作者头像 李华
网站建设 2026/4/15 20:48:43

SiameseUIE效果展示:5类测试样例结果截图与人工评估准确率报告

SiameseUIE效果展示&#xff1a;5类测试样例结果截图与人工评估准确率报告 1. 为什么这次我们不讲部署&#xff0c;只看效果&#xff1f; 你可能已经看过不少模型部署教程——环境怎么配、依赖怎么装、命令怎么敲。但真正决定一个信息抽取模型能不能用的&#xff0c;从来不是…

作者头像 李华