spinlock是仅适用于临界区短于1微秒且线程切换开销更高的特殊struct锁,必须用ref传参、禁止拷贝、不可跨方法配对enter/exit、单核下禁用、无超时与中断支持,价值在于微秒级确定性延迟。

SpinLock 不是 lock 的替代品,而是只在临界区短于 1 微秒、且线程切换开销明显高于自旋时才值得考虑的特殊工具。
SpinLock 必须用 ref 传参,否则会复制 struct 导致锁失效
SpinLock 是 struct,不是 class。直接赋值或传参不加 ref,等于拷贝一份新实例 —— 你 Enter 的是副本,Exit 的也是副本,原始锁状态完全没变,其他线程永远等不到释放。
- 声明必须是字段或
ref局部变量:private SpinLock _lock = new SpinLock(); - 调用
Enter时必须传ref:_lock.Enter(ref taken); - 不能写成
var copy = _lock; copy.Enter(ref taken);—— 这毫无同步意义 - 异步方法中尤其危险:局部
SpinLock变量 +await后续操作,极易因栈复制导致锁丢失
SpinLock.Enter 之后必须配对 Exit,且不能跨方法边界
SpinLock 没有 using 支持,也不像 lock 那样自动保证释放。一旦 Enter 成功(taken == true),就必须在同一线程、同一调用栈深度内调用 Exit,否则锁永久挂起,其他线程无限自旋。
- 禁止在
try外Enter,或在catch/finally外Exit - 不能把
Enter和Exit拆到两个不同方法里(比如AcquireLock()/ReleaseLock()) - 推荐模式:
bool taken = false; try { _lock.Enter(ref taken); /* 临界区 */ } finally { if (taken) _lock.Exit(); } - 注意:如果临界区抛异常,
taken仍为true,必须在finally中检查并Exit
单核 CPU 上 SpinLock 几乎必然恶化性能
自旋的本质是“靠 CPU 空转等锁”,但单核环境下,持有锁的线程不运行,自旋线程就永远无法推进 —— 它占着 CPU 却干不了事,还堵死了真正能干活的线程。.NET 内部虽有 Thread.Yield 补救,但效果有限。
- 开发/测试环境若为虚拟机或低配笔记本(尤其 macOS/Linux 的 QEMU/VirtualBox),默认可能只有 1 个逻辑核心
- 即使物理多核,若进程被
ProcessorAffinity锁定到单核,同样踩坑 - 没有运行时检测手段,只能靠部署环境确认:用
Environment.ProcessorCount做前置 guard,ProcessorCount == 1时直接退回到lock - 别指望 “它自己会聪明地切走”——SpinLock 的设计哲学就是不切,切了就违背初衷
SpinLock 无超时、不响应中断、不参与线程调度公平性
它不和 CLR 线程调度器打交道,也不向 OS 发起任何等待系统调用。这意味着:
- 没有
TryEnter(int millisecondsTimeout)重载;TryEnter只有无参版(立即返回 bool)和ref bool版 - 线程被
Thread.Interrupt()或Thread.Abort()(已过时)也无法打断自旋循环 - 高争用下,某些线程可能连续自旋几十万次才抢到锁,而其他线程始终被饿死 —— 它不保证 FIFO 或公平性
- 真实业务中若需超时或可取消,必须外层封装:用
Stopwatch计时 +TryEnter循环,但此时已失去 SpinLock 的原始优势
最常被忽略的一点:SpinLock 的价值不在“快”,而在“确定性”。它的延迟抖动极小(微秒级可控),适合实时性敏感的底层组件(如高性能队列、内存池分配器)。一旦临界区出现分支判断、IO 调用、或哪怕一次虚方法分派,就该立刻换回 lock —— 此时自旋已不是优化,而是惩罚。











