因为std::priority_queue是单层堆,不支持进程在多级队列间动态迁移、优先级实时调整及同级FIFO顺序,硬套会导致降级开销爆炸且丧失MFQ行为反馈机制。

为什么 std::priority_queue 不能直接用于多级反馈队列(MFQ)?
因为 std::priority_queue 是单层、静态优先级的容器,不支持动态调整优先级、跨队列迁移、时间片重置等 MFQ 的核心行为。你如果直接用它存所有进程,就等于把 3 级队列强行压成 1 级——剥夺逻辑和反馈机制全失效。
真正可行的做法是:用 std::vector<:queue>> 或 std::array<:queue>, N> 显式管理每一级就绪队列,再配合一个全局时钟(int current_time)驱动调度循环。
- 第 0 级队列用最短时间片(如 2),新进程或被剥夺的进程必须从这里入队
- 每级队列的时间片按指数增长(如 2, 4, 8),可通过
quantum[i] = base_quantum * (1 计算 - 进程在某级队列耗尽时间片后,若未完成,降级到下一级(
i+1),但不能越级跳转 - 被 I/O 中断的进程返回时,**不降级**,而是回到原队列尾部(这是很多实现漏掉的关键点)
如何正确模拟“优先级剥夺”而不引发无限抢占?
剥夺只发生在高优先级队列(低索引)有新进程就绪,且当前正在运行的进程属于更低优先级队列(高索引)时。关键不是“谁优先级高”,而是“谁在更靠前的队列里”。
不要在每次插入新进程时立刻中断当前运行进程——这会导致抖动。应该只在调度器空闲(current_process == nullptr)或当前进程时间片耗尽时,才重新选下一个运行进程。
- 维护一个
Process* current_process指针,记录正在 CPU 上执行的进程(可为nullptr) - 每次 tick 前检查:若
current_process非空且其剩余时间片 > 0,继续执行;否则调用select_next() -
select_next()从第 0 级开始遍历,找到第一个非空队列的队首进程;找不到则 CPU 空闲 - 剥夺仅在此刻发生:如果之前有进程在运行,而新选中的进程来自更高级别队列,才算真正剥夺
Process 结构体必须包含哪些字段才能支撑 MFQ 逻辑?
光有 pid、burst_time、arrival_time 不够。缺少运行上下文,调度器无法判断何时降级、是否该剥夺、还剩多少时间片。
最小必要字段包括:
-
int pid—— 进程唯一标识 -
int arrival_time—— 到达系统时刻(用于判断是否已就绪) -
int burst_time—— 总需 CPU 时间 -
int remaining_time—— 当前还需多少 CPU 时间(随执行递减) -
int queue_level—— 当前所在队列级别(0 开始),决定时间片大小和调度顺序 -
int time_in_queue—— 在当前队列中已等待时间(可选,用于响应比计算) -
int last_run_start—— 上次开始执行的时间戳(用于计算周转/等待时间)
注意:remaining_time 和 burst_time 必须分开维护;很多初学者误用 burst_time 减法,导致无法统计原始总耗时。
模拟输出中常见的“等待时间翻倍”或“完成时间错乱”怎么排查?
这类问题几乎都源于时间推进逻辑与事件触发时机不匹配。MFQ 模拟不是纯离散事件,而是混合模型:CPU 执行按 tick 推进,但进程到达、I/O 完成等是外部事件,必须严格对齐时间轴。
- 确保所有“时间推进”只发生在统一位置:比如主循环的
for (int t = 0; t ,而不是在每个队列操作里自增 - 进程到达事件必须在
t == arrival_time时触发入队,不能延迟到下一个 tick - 当进程因 I/O 阻塞时,要把它从就绪队列移出,并加入一个
std::vector<:pair process>>类型的阻塞队列,其中first是唤醒时间(t + io_duration) - 每个 tick 开始时,先处理所有唤醒时间 ≤ 当前
t的阻塞进程,再检查就绪队列,最后执行调度
最容易被忽略的是:I/O 完成后进程回到就绪队列时,queue_level 应保持不变,且应插入到对应队列**尾部**(FIFO),而非头部——否则会破坏公平性,造成隐式高优。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











