
多级反馈队列(MLFQ)的核心逻辑不是“写一个类”,而是维护多个就绪队列 + 动态升降级规则
直接套用标准库容器就能搭出骨架,但关键在调度决策点:什么时候把任务从高优先级队列挪到低优先级?什么时候允许“饥饿”任务回升?C++里没有现成的 mlfq_scheduler,得自己控制队列切换时机和时间片分配。
典型做法是用 std::vector<:queue>></:queue> 表示多级队列,每级对应不同时间片长度(比如第 0 级 1ms,第 1 级 2ms,第 2 级 4ms……),新任务一律进最高优先级;每次调度选非空的最高级队列头任务执行。
- 任务被抢占或自愿让出(如 I/O 阻塞)时,若仍在当前级未用完时间片,**不降级**——这是避免频繁抖动的关键
- 任务耗尽本级时间片后,才降到下一级(
level = std::min(level + 1, max_level - 1)) - 为防饥饿,可设全局计时器,每隔几秒把所有低级队列里的任务提到最高级(或只提等待超时的)
时间片耗尽判断不能依赖系统时钟差值,要用“执行中计数器”
用 std::chrono::steady_clock::now() 做时间片切片,在高负载或任务密集场景下误差大:函数调用开销、线程调度延迟都会导致实际执行时间远超设定值,造成误降级甚至饿死。
更稳的方式是在任务结构体里加字段 remaining_quantum,每次调度前设为当前级时间片值,执行中每处理一个单位工作(如一次循环迭代、一条指令模拟)就减 1;减到 0 就强制切换。
立即学习“C++免费学习笔记(深入)”;
- 适用于 CPU 密集型模拟场景(如教学 OS 内核、仿真器)
- 若任务本身是真实系统调用(如
read()),需配合信号或协程中断机制,否则无法精确截断 - 别用
sleep_for模拟时间片——它会让线程挂起,破坏“抢占”语义
Task 结构体必须携带 level 和 last_run_time,否则升降级逻辑失效
只存优先级数字不够。MLFQ 的“反馈”体现在行为历史:同一任务多次被降级后,应比新任务更难再升回高级队列,否则会劣化为轮转调度。所以 Task 至少要含:
-
int level:当前所在队列索引 -
std::chrono::steady_clock::time_point last_run_time:上次被调度的时间戳,用于计算等待时长以触发饥饿提升 -
int consecutive_degrades(可选):连续降级次数,用于抑制过度回升
漏掉 last_run_time 就没法实现“等待超时自动提级”,容易让后台任务永远卡在最低级——这点在调试时很难一眼发现,日志里只看到“任务一直没运行”,但查不到原因。
std::priority_queue 不适合做 MLFQ 的队列容器
看起来很自然:用优先级队列按 level 排序,自动取最高优任务。但问题在于——MLFQ 要求同级任务 FIFO,而 std::priority_queue 对相同优先级的元素顺序不保证;更麻烦的是,它不支持随机访问或遍历,无法实现“扫描所有低级队列找等待超时任务”这类操作。
正确选择是:std::vector<:queue>></:queue> 或 std::array<:queue>, N></:queue>,用下标代表级别,每个 std::queue 天然满足 FIFO。
- 别为了“省代码”用
std::priority_queue<task std::vector>, CompareLevel></task>——后续加饥饿提升、批量迁移逻辑时会卡死 - 如果真需要按到达时间排序同级任务,改用
std::deque并手动维护插入位置,比强扭 priority_queue 更可控
真正难的不是写完五级队列,而是让“反馈”可观察:加个简单计数器统计每级进出次数,跑两轮就知道有没有任务在某一级反复打转。没有这层验证,算法只是纸面上的正确。










