队列调度中的公平性和饥饿问题是什么?
简化版
队列公平性指任务能否按合理顺序获得处理机会;饥饿指某些任务长期得不到执行。普通 FIFO 比较公平,但优先级队列、多个队列竞争、锁队列调度都可能让低优先级任务长期等待,需要老化、配额或轮询等策略缓解。
详细版
队列不只是存数据,也常用于调度。调度时除了吞吐,还要考虑公平。
典型问题:
- FIFO:先到先服务,简单公平,但不能表达紧急任务。
- 优先级队列:高优先级先执行,但低优先级可能饥饿。
- 多队列:不同来源任务可能互相挤压。
- 锁等待队列:非公平锁可能让新来的线程插队。
解决思路包括优先级老化、时间片轮转、加权轮询、最大等待时间和监控等待分布。
完整版教学
一、队列为什么会参与调度
很多系统中,队列决定“谁先被处理”。线程池任务队列、消息消费队列、网络请求队列、锁等待队列,本质上都在做调度。
如果只看数据结构,队列是容器;如果看系统行为,队列是秩序。
任务进入队列 -> 队列规则排序 -> 执行器取任务
规则不同,系统表现完全不同。FIFO、优先级、轮询、多级反馈队列,都是在回答“下一个轮到谁”。
二、FIFO 公平但不总是最优
FIFO 先到先服务,直觉上很公平。先来的任务先处理,后来的任务后处理。
但 FIFO 也有问题。如果队头是一个耗时 10 秒的大任务,后面 100 个耗时 1 毫秒的小任务都要等它。这叫队头阻塞。
[10s任务, 1ms, 1ms, 1ms ...]
所以公平不等于低延迟。调度设计经常要在公平、吞吐、平均延迟和尾延迟之间取舍。
三、优先级队列为什么可能导致饥饿
优先级队列会优先处理高优先级任务。如果高优先级任务源源不断,低优先级任务可能一直排不到。
例如:
每秒进入 100 个高优先级任务
消费者每秒只能处理 100 个任务
低优先级任务永远没有机会
这就是饥饿。它不一定是 bug,而是策略缺陷:系统规则允许某类任务长期没有服务机会。
四、如何缓解饥饿
常见办法是让等待时间影响调度权重。
| 策略 | 做法 | 适合场景 |
|---|---|---|
| 优先级老化 | 等越久优先级越高 | 防低优先级饥饿 |
| 时间片轮转 | 每类任务轮流处理 | 多租户或多来源 |
| 加权轮询 | 高权重多处理,低权重也有份 | 不同业务等级 |
| 最大等待时间 | 超过阈值强制提升 | 有 SLA 的任务 |
这些策略的目标不是取消优先级,而是防止优先级把某些任务永久压死。
五、公平锁和非公平锁也是队列问题
锁竞争时,等待线程也可以排队。公平锁倾向按等待顺序唤醒线程;非公平锁允许新来的线程尝试插队。
公平锁减少饥饿,但可能降低吞吐,因为严格排队会增加上下文切换和调度成本。非公平锁吞吐可能更高,但某些线程在极端情况下等待更久。
公平:排队买票
非公平:窗口空了,新来的人也可能抢到
这说明公平性往往不是免费午餐。
六、怎么监控公平性
平均等待时间不够。一个系统平均等待 10ms,也可能有少数任务等了 10 秒。要看分位数、最大等待时间、各优先级队列长度和被拒绝/超时次数。
记忆钩子:队列调度问的不只是“谁在前”,还要问“有没有人永远轮不到”。
七、常见误区与追问
- 误区:FIFO 一定是最佳公平。 FIFO 简单公平,但可能被慢任务造成队头阻塞。
- 误区:优先级越高系统越好。 高优先级持续涌入会让低优先级饥饿。
- 误区:公平策略没有成本。 公平可能牺牲吞吐或增加调度开销。
- 追问:如何防止低优先级任务饿死? 用老化、配额、加权轮询或最大等待时间。
- 追问:看平均延迟够吗? 不够,要看分位数和最大等待时间,尤其关注低优先级任务。
八、加强记忆
队列一旦用于调度,就不只是先进先出的小容器,而是资源分配规则。FIFO 简单但可能队头阻塞,优先级灵活但可能饥饿,公平锁稳定但可能牺牲吞吐。回答时抓住“公平、饥饿、代价、监控”四个点。