Hotdry.
systems-engineering

自旋锁 vs 互斥锁:自旋还是睡眠的阈值决策

混合锁的核心决策逻辑:低争用短临界区短暂自旋,高争用长持锁回退futex睡眠,通过perf/strace经验调优阈值。

在多线程并发编程中,选择自旋锁(spinlock)还是互斥锁(mutex)是优化性能的关键决策。核心原则是:临界区(CS)执行时间短且争用低时,自旋等待避免上下文切换开销;反之,睡眠让出 CPU 给其他线程。现代 pthread_mutex 已内置自适应混合策略,先短暂自旋再 futex 睡眠,本文聚焦决策框架、阈值调优与落地参数。

自旋锁与互斥锁机制对比

自旋锁通过用户态原子操作(如 C11 的 atomic_compare_exchange_weak 或 x86 LOCK CMPXCHG)实现忙等待:线程循环尝试 CAS(Compare-And-Swap)抢锁,直到成功,无 syscall 开销(25-50ns uncontended),但失败时 100% CPU 消耗,每失败一次缓存线弹跳40-80ns。

互斥锁(pthread_mutex_lock)基于 Linux futex:无争用时纯用户态原子操作;争用时 futex (FUTEX_WAIT) syscall 睡眠(~500ns + 3-5μs 上下文切换),释放时 futex (FUTEX_WAKE) 唤醒。glibc 实现自适应自旋:若锁持有者正运行(通过任务 ID 检查),短暂自旋(默认 30 次 PAUSE 循环,~ 几百 cycles)再睡眠,避免立即切换。

证据显示,自旋锁适合 CS<100ns、低争用(2-4 线程);mutex 适合 CS>10μs 或高争用。PostgreSQL LWLock 用自旋短查询,mutex 长 IO;Redis 用自旋微秒队列。

风险与极限

自旋风险:高争用下 CPU 浪费、优先级反转(低优先线程持锁,高优先自旋饿死)、用户态抢占(线程持锁被 preempt,其他自旋 100ms)。mutex 风险:唤醒延迟抖动(thundering herd,多个线程竞争唤醒)。

极限:NUMA 多 socket 下缓存远程访问放大自旋成本;实时系统用 PI-mutex 防反转。

决策阈值与调优参数

核心阈值:预期 CS 持锁时间 vs 上下文切换成本(~3-5μs)。若持锁 < 切换成本,自旋胜出。

可落地阈值清单:

  1. 自旋时长:初始 100-500 cycles(50-200ns@3GHz),用 rdtsc 测量:uint64_t start=rdtsc(); /*CS*/ uint64_t cycles=rdtsc()-start;。glibc 默认30 PAUSE(x86 pause 指令减缓自旋,防缓存风暴)。
  2. 争用阈值:线程数 <核心数 * 2,低争用自旋;>8 线程或 > 10% 失败率,回退睡眠。监控 futex 调用:strace -c your_app,> 百万 /sec 热锁需分片。
  3. 自适应参数(自定义 hybrid 锁):
    int spins = 0;
    while (atomic_cmpxchg(&lock, 0, 1) != 0) {
        if (++spins > 100) { futex_wait(&lock, 1); break; }
        pause();  // 或 __builtin_ia32_pause()
    }
    
    调优:spins=50 低争用,200 中争用;backoff 指数退避(1<<spins%10)。
  4. 缓存对齐alignas(64) atomic_int lock; 防 false sharing。
  5. 持锁检查:预估 CS:短读 / 计算用自旋,长 IO/alloc 用 mutex。

监控与回滚策略:

  • perf:perf stat -e context-switches,cache-misses,cycles your_app。高 ctxsw 低 CPU→mutex 优化;高 miss 100% CPU→自旋 + 对齐。
  • /proc/PID/status:voluntary_ctxt_switches 高→mutex 正常;involuntary 高→自旋 preempt 问题。
  • 阈值经验:基准测试变 NUM_THREADS=2/8/16,HOLD_NS=50/500/5000,选 ops/sec 最高者。
  • 回滚:默认 pthread_ADAPTIVE_NP mutex(PTHREAD_MUTEX_ADAPTIVE_NP),fallback 纯 mutex。

生产实践清单

  1. 基准代码:编译 spinlock_test/mutex_test(O2 -pthread),top 观察 CPU,strace futex 数。
  2. 分层锁:读多写少用 RWLock(自旋 fastpath)。
  3. 无锁备选:高热用 RCU 或 lock-free queue。
  4. 实时:PREEMPT_RT 内核 + PI mutex。

通过上述参数,hybrid 锁在 Redis/PostgreSQL 中将尾延迟降 50%,CPU 效升 30%。最终,勿硬编码阈值:profile 你的负载,迭代调优。

资料来源:

查看归档