OSSpinLock 被废弃的真实原因在于它在 iOS 的线程调度机制下存在严重的 优先级反转(Priority Inversion) 风险,这会导致应用出现逻辑上的死锁或永久性卡顿。
虽然从代码逻辑上看,它并不像 dispatch_semaphore 在同一串行队列等待那样属于“经典死锁”,但其表现结果是一致的。
核心原因:忙等(Busy-wait)与调度算法的冲突
OSSpinLock 是一种自旋锁。当一个线程尝试获取已被占用的锁时,它不会进入休眠,而是处于“忙等”状态,持续循环检查锁是否可用,以换取极低的上下文切换开销。
然而,iOS 的调度器(Scheduler)采用的是基于优先级的抢占式调度,这引发了灾难性的后果:
- 低优先级线程持有锁:一个低优先级线程获取了
OSSpinLock并开始执行临界区代码。 - 高优先级线程抢占:此时一个高优先级线程进入就绪状态,由于优先级更高,系统立即剥夺低优先级线程的 CPU 时间片,转而运行高优先级线程。
- 死锁产生:高优先级线程尝试获取该
OSSpinLock。由于锁被占用,它开始忙等(自旋) 。 - 因果循环:因为高优先级线程正在 CPU 上疯狂自旋,系统调度器认为它非常繁忙,从而永远不会把 CPU 时间片分配回给低优先级线程。
- 结果:低优先级线程因拿不到 CPU 时间而无法完成任务并释放锁;高优先级线程则永远在自旋等待一个无法被释放的锁。
为什么信号量或互斥锁相对安全?
相比之下,dispatch_semaphore 或 pthread_mutex 在等待时会让线程进入休眠状态:
- 当高优先级线程休眠时,它会出让 CPU 资源。
- 系统因此有机会让低优先级线程运行并释放锁。
- 现代内核(如 iOS 的 Mach 内核)还支持 优先级继承(Priority Inheritance) :如果高优先级线程正在等待一个互斥锁,内核会临时提升持有锁的低优先级线程的优先级,确保它能尽快执行完并放锁。而
OSSpinLock并不支持这种内核级的优先级优化。
替代方案
苹果在废弃 OSSpinLock 后,推荐使用以下方案:
| 方案 | 特点 | 适用场景 |
|---|---|---|
os_unfair_lock | 官方直接替代品。它在竞争时会休眠而非自旋,且解决了优先级反转问题。 | 追求极致性能,替代原有的 OSSpinLock。 |
pthread_mutex | 跨平台,支持递归锁配置。 | 复杂的、需要递归加锁的场景。 |
@synchronized | 简单易用,但性能相对较低。 | 对性能不敏感的简单同步。 |
总结
OSSpinLock 的废弃不是因为它“慢”,而是因为它“太快”且“太自私”,在优先级调度环境下会通过抢占 CPU 时间片活生生“饿死”持有锁的线程,最终导致整个进程卡死。