在自旋锁(Spinlock)中,优先级反转(Priority Inversion) 并非传统意义上的“多个线程竞争”,而是一种由于“忙等”机制导致的死锁状态。
其产生的核心逻辑如下:
1. 产生的前提条件
- 基于优先级的抢占式调度:系统总是让优先级最高的就绪线程占用 CPU。
- 自旋(Busy-wait)机制:线程在获取不到锁时,不进入休眠,而是持续占用 CPU 循环检查锁的状态。
- 缺乏优先级继承(Priority Inheritance) :自旋锁是底层的同步原语,内核不会因为高优先级线程在等待而临时提升持有锁线程的优先级。
2. 演变过程:从竞争到死锁
我们可以通过三个角色来还原现场:线程 L(低优先级) 、线程 H(高优先级) 和 自旋锁 Lock。
-
持有锁:低优先级线程 L 获取了
OSSpinLock,进入临界区开始执行代码。 -
抢占:此时,高优先级线程 H 变为就绪状态。由于 H 优先级更高,操作系统内核立即强行剥夺 L 的 CPU 时间片,转而执行 H。
-
自旋等待:线程 H 执行过程中尝试获取同一个
OSSpinLock。发现锁被占用,于是 H 开始原地自旋(消耗 CPU 循环检测) 。 -
因果断裂(死锁形成) :
- H 层面:H 正在 CPU 上飞速运转,调度器认为 H 任务紧急且繁忙,因此不断给 H 分配 CPU 时间。
- L 层面:由于 CPU 一直被 H 占用,L 无法获得任何执行机会。
- 死结:L 不执行就无法释放锁,L 不释放锁 H 就永远在自旋。
3. 为什么互斥锁(Mutex)能避开?
与自旋锁不同,互斥锁或信号量在获取不到锁时会主动让出 CPU:
- 休眠:当高优先级线程 H 发现互斥锁被占用时,它会进入休眠状态(Wait)。
- 让权:一旦 H 休眠,它就不再占用 CPU。调度器此时会将 CPU 分配给目前优先级最高的就绪线程,即低优先级的 L。
- 释放:L 获得 CPU 后完成任务并释放锁,随后唤醒 H。
总结:自旋锁的“自私”导致了毁灭
自旋锁的设计初衷是假设“临界区非常短,稍等片刻就能拿到”。但在 iOS 这种严格区分优先级的系统中,高优先级的“稍等片刻”(自旋)恰恰剥夺了低优先级线程“把锁还回来”的唯一机会。
正如苹果工程师在讨论 OSSpinLock 废弃时所言:自旋锁在高优先级线程看来是一个永远无法满足的条件。