4-12.【OC】【锁】pthread_mutex 的 adaptive spinning 是什么?

1 阅读3分钟

在高性能锁的设计中,Adaptive Spinning(自旋自适应) 是一种混合策略,旨在平衡“空转等待”的低延迟和“挂起线程”的低功耗。

简单来说,它让 pthread_mutex 变得“聪明”了:它不再是上来就直接让线程睡觉,而是先观察一下,看能不能通过几微秒的自旋直接拿到锁。


1. 为什么需要自适应?

在多核处理器中,线程同步通常面临两种选择:

  • 自旋(Spinning): CPU 循环执行 NOP 或忙等。优点是响应极快,没有上下文切换(Context Switch)开销;缺点是极其耗电,且如果锁持有时间长,会浪费 CPU 周期。
  • 阻塞(Blocking): 线程进入休眠,交出 CPU。优点是不占资源;缺点是唤醒时需要内核参与,上下文切换大约需要几微秒(对于纳秒级的操作来说太慢了)。

Adaptive Spinning 的逻辑: 如果锁的持有者正在另一个 CPU 核心上运行,那么它很有可能马上就会释放锁。此时自旋一小会儿是划算的。


2. 工作原理:观察与反馈

当线程尝试获取一个 PTHREAD_MUTEX_ADAPTIVE_NP(非标准扩展)类型的锁时,它会经历以下阶段:

  1. 观察环境: 检查锁的持有者是否正在运行。如果持有者已经挂起(Sleeping),当前线程会直接选择放弃自旋进入阻塞,因为持有者醒来并释放锁的时间一定很长。

  2. 动态自旋: 如果持有者正在运行,当前线程会开始自旋。自旋的次数不是固定的,而是根据历史成功率动态调整。

    • 如果最近几次自旋都很快拿到了锁,系统会增加下次自旋的次数上限。
    • 如果最近自旋总是失败最后还是得靠阻塞解决,系统会减少自旋次数,甚至直接跳过自旋。
  3. 最终降级: 如果达到自旋上限仍未获得锁,线程才会执行 syscall 进入内核态挂起。


3. iOS 中的情况:os_unfair_lock 的异曲同工

虽然 pthread_mutex 在 Linux 等系统上广泛支持自适应模式,但在 iOS/macOS 平台上,苹果更倾向于推行 os_unfair_lock

os_unfair_lock 实际上也包含了类似的逻辑:

  • 它在用户态进行极短时间的自旋。
  • 它解决了 OSSpinLock优先级反转问题:如果高优先级线程在自旋等待低优先级线程,内核会感知到这种竞争,并将低优先级线程的优先级临时提升,让其尽快完成任务并释放锁。

4. 性能对比

策略场景CPU 占用响应速度
纯自旋 (Spinlock)极短的任务极高最快
纯互斥 (Mutex)长时间的任务极低慢(受限于上下文切换)
自适应 (Adaptive)绝大多数场景中等接近自旋,安全性接近互斥

5. 什么时候该手动开启?

在 POSIX 接口中,你需要显式设置属性:

C

pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
// PTHREAD_MUTEX_ADAPTIVE_NP 在某些 Unix 系统中可用
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ADAPTIVE_NP);
pthread_mutex_init(&lock, &attr);

注意: 在 iOS 开发中,NSLock 默认并不开启自适应自旋,它主要基于底层的 pthread_mutex_lock 默认实现。如果你在写极高性能的底层 C++ 库,手动配置自适应属性会有显著提升。


总结

Adaptive Spinning 是锁的一种**“博弈策略”**。它利用历史经验预测未来,从而在“浪费 CPU 周期”和“浪费上下文切换时间”之间找到最优平衡点。