std::priority_queue 是实现最简优先级任务调度的轻量方案,底层基于堆,插入和弹出为 O(log n),需自定义比较器使小数字优先;不支持动态修改优先级,多线程需手动加锁并避免锁内执行任务。

用 std::priority_queue 实现最简优先级任务调度
直接上手最常用、最轻量的方案:std::priority_queue 是 C++ 标准库中为优先级队列设计的容器适配器,底层默认用 std::vector + 堆(heap)实现,插入和弹出时间复杂度都是 O(log n)。它不支持随机访问或遍历,但对“取最高优先级任务→执行→再取下一个”这种典型调度场景足够高效。
关键点在于:优先级不是越大越先执行,而是由比较器决定。默认是最大堆(std::less),即 top() 返回最大元素;但任务调度通常希望数字越小代表优先级越高(比如 0 = 紧急,10 = 低优先级),所以得反着来:
struct Task {
int priority;
std::function<void()> fn;
// 重载 operator< 使 priority 小的排在前面(最小堆语义)
bool operator<(const Task& other) const {
return priority > other.priority; // 注意是 >
}
};
std::priority_queue<Task> task_queue;
task_queue.push({0, []{ printf("EMERGENCY!\n"); }});
task_queue.push({5, []{ printf("normal job\n"); }});
task_queue.push({1, []{ printf("high priority\n"); }});
// pop 顺序:priority=0 → 1 → 5
为什么不能直接用 std::queue 或 std::deque?
因为它们没有内置优先级逻辑,插入就是尾部追加,取出永远是 FIFO(先进先出),无法按优先级动态排序。强行每次插入后手动排序(比如用 std::sort)会导致每次插入变成 O(n log n),性能断崖式下降,尤其在高频提交任务时明显卡顿。
常见错误现象包括:
立即学习“C++免费学习笔记(深入)”;
- 任务提交后执行顺序始终不变,像普通队列一样线性执行
- 手动维护 vector 并调用
std::sort,CPU 占用飙升,延迟毛刺严重 - 误以为
std::priority_queue支持修改已有元素优先级——它不支持,修改后必须重新 push/pop
需要动态调整任务优先级?用 std::set 或 boost::heap
std::priority_queue 是只读 top + 插入/弹出的封闭结构,一旦任务入队,它的 priority 就锁死了。如果业务要求“正在等待的任务被临时提权”,就得换容器:
-
std::set<Task, Compare>:利用红黑树有序性,支持erase+insert模拟更新(注意:不能原地改 key,必须先删再插) -
boost::heap::fibonacci_heap:支持decrease_key操作,适合频繁提权场景,但引入 Boost 依赖 - 自己封装一个带索引的堆(indexed heap):用
std::vector存数据,额外std::unordered_map记录每个任务当前下标,实现 O(log n) 更新——适合对性能和可控性要求极高的系统
别低估这个差异:游戏引擎里 NPC 行为调度、嵌入式设备中断响应队列,都可能遇到运行中优先级变更,硬套 std::priority_queue 会绕弯路甚至重构。
多线程环境下必须加锁,且注意虚假唤醒和竞争条件
真实调度器几乎总在多线程中使用:一个线程提交任务(push),另一个线程循环取任务(top+pop)。这时 std::priority_queue 本身不是线程安全的,必须显式加锁:
std::mutex queue_mutex;
std::priority_queue<Task> task_queue;
std::condition_variable cv;
bool shutdown_requested = false;
// 提交线程
void submit_task(const Task& t) {
std::lock_guard<std::mutex> lk(queue_mutex);
task_queue.push(t);
cv.notify_one(); // 唤醒等待线程
}
// 执行线程
void worker_loop() {
while (!shutdown_requested) {
Task t;
{
std::unique_lock<std::mutex> lk(queue_mutex);
cv.wait(lk, [&]{ return !task_queue.empty() || shutdown_requested; });
if (shutdown_requested && task_queue.empty()) break;
t = std::move(task_queue.top());
task_queue.pop();
}
t.fn(); // 在锁外执行,避免阻塞其他线程
}
}
容易踩的坑:
- 在锁内执行
t.fn():万一回调函数耗时长或死锁,整个队列被堵住 - 用
notify_all替代notify_one:多个 worker 线程同时被唤醒,但只有一个能抢到任务,其余空转消耗 CPU - 没处理
shutdown_requested和empty()的竞态:可能导致 wait 返回后队列已空,top()崩溃
真正难的不是堆排序逻辑,而是把优先级语义、线程安全、生命周期管理(比如任务对象捕获的变量是否还有效)这几层拧在一起不出错。很多崩溃不是算法问题,是 move 之后又用了悬挂引用,或者 lambda 捕获了栈变量却在线程里延后执行。


















