优先级反转不是“低优先级线程运行得比高优先级线程久”这么简单。真正的问题是:低优先级线程持有一个高优先级线程必须获得的锁;高优先级线程因此阻塞;另一个中优先级线程却不断抢占持锁者。最高优先级任务最终被一个与它无关的中优先级任务间接挡住,阻塞时间也变得难以预估。

低优先级线程持锁高优先级线程阻塞rtmutex 传播高优先级持锁者被加速运行解锁并恢复原优先级

三个线程如何造成反转

设 L、M、H 分别为低、中、高优先级线程。L 先拿到互斥锁并准备访问共享缓冲区;H 到来后需要同一把锁,于是睡眠等待;M 则无需这把锁,持续占用 CPU。没有优先级继承时,L 得不到 CPU 释放锁,H 也无法运行。此时 H 的实际阻塞时间取决于 M 做多久,而不是 L 的临界区本身有多短。

L:持锁者原本优先级低,但负责尽快完成临界区M:干扰者不需要锁,却可能阻止 L 被调度H:实时等待者需要锁,无法直接抢占已被阻塞的 L优先级继承将 H 的有效优先级临时传给 L解锁后L 恢复原优先级,H 获得锁继续执行仍要控制继承只能缓解调度反转,不能缩短过长临界区

Linux 内核的 rtmutex 将这种优先级继承扩展到等待链:若 L 又在等待另一个锁,优先级可以继续向持锁链传递。PREEMPT_RT 中许多可转换的自旋锁会利用这套机制,因此锁设计直接影响实时性。

用户态锁也要有协议

普通 pthread_mutex_t 默认不一定启用优先级继承。对确实跨优先级共享、且临界区很短的资源,可以在创建锁时明确选择 PTHREAD_PRIO_INHERIT。是否支持及其代价取决于系统和运行环境,所以要在目标机上检查返回值并做压力测试。

1
2
3
4
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
pthread_mutex_init(&shared_lock, &attr);

这不是所有锁都该加的魔法属性。大量复杂锁、长 I/O 或回调都放在同一临界区,即使有继承也会让高优先级线程等很久。更好的方案常常是复制快照、无锁队列、双缓冲或把慢操作移到锁外。

降低锁风险的四条规则

  1. 给共享资源制定唯一的锁顺序,避免嵌套锁形成死锁与长继承链。
  2. 临界区只做内存读写,不做磁盘、网络、日志或未知时长的函数调用。
  3. 将“控制路径必须等待的数据”与“后台统计/显示数据”分离。
  4. 用 trace 或业务日志记录锁等待的最大值,而不是只看平均时间。

优先级继承解决的是“谁应该先拿到 CPU”问题;共享设计决定的是“高优先级任务为什么非要等待”。后者往往更值得先优化。

参考:RT-mutex design · pthread_mutexattr_setprotocol(3p)