4-20.【OC】【锁】OSSpinLock 被废弃的真实原因是什么?

26 阅读3分钟

OSSpinLock 被废弃的真实原因在于它在 iOS 的线程调度机制下存在严重的 优先级反转(Priority Inversion) 风险,这会导致应用出现逻辑上的死锁或永久性卡顿。

虽然从代码逻辑上看,它并不像 dispatch_semaphore 在同一串行队列等待那样属于“经典死锁”,但其表现结果是一致的。

核心原因:忙等(Busy-wait)与调度算法的冲突

OSSpinLock 是一种自旋锁。当一个线程尝试获取已被占用的锁时,它不会进入休眠,而是处于“忙等”状态,持续循环检查锁是否可用,以换取极低的上下文切换开销。

然而,iOS 的调度器(Scheduler)采用的是基于优先级的抢占式调度,这引发了灾难性的后果:

  1. 低优先级线程持有锁:一个低优先级线程获取了 OSSpinLock 并开始执行临界区代码。
  2. 高优先级线程抢占:此时一个高优先级线程进入就绪状态,由于优先级更高,系统立即剥夺低优先级线程的 CPU 时间片,转而运行高优先级线程。
  3. 死锁产生:高优先级线程尝试获取该 OSSpinLock。由于锁被占用,它开始忙等(自旋)
  4. 因果循环:因为高优先级线程正在 CPU 上疯狂自旋,系统调度器认为它非常繁忙,从而永远不会把 CPU 时间片分配回给低优先级线程。
  5. 结果:低优先级线程因拿不到 CPU 时间而无法完成任务并释放锁;高优先级线程则永远在自旋等待一个无法被释放的锁。

为什么信号量或互斥锁相对安全?

相比之下,dispatch_semaphorepthread_mutex 在等待时会让线程进入休眠状态

  • 当高优先级线程休眠时,它会出让 CPU 资源。
  • 系统因此有机会让低优先级线程运行并释放锁。
  • 现代内核(如 iOS 的 Mach 内核)还支持 优先级继承(Priority Inheritance) :如果高优先级线程正在等待一个互斥锁,内核会临时提升持有锁的低优先级线程的优先级,确保它能尽快执行完并放锁。OSSpinLock 并不支持这种内核级的优先级优化。

替代方案

苹果在废弃 OSSpinLock 后,推荐使用以下方案:

方案特点适用场景
os_unfair_lock官方直接替代品。它在竞争时会休眠而非自旋,且解决了优先级反转问题。追求极致性能,替代原有的 OSSpinLock
pthread_mutex跨平台,支持递归锁配置。复杂的、需要递归加锁的场景。
@synchronized简单易用,但性能相对较低。对性能不敏感的简单同步。

总结

OSSpinLock 的废弃不是因为它“慢”,而是因为它“太快”且“太自私”,在优先级调度环境下会通过抢占 CPU 时间片活生生“饿死”持有锁的线程,最终导致整个进程卡死。