需将函数对象包装为 std::function 并自定义比较器,因 std::priority_queue 要求元素可比较,而 std::function 无默认 operator

怎么用 std::priority_queue 存任务
它不能直接存函数对象或 lambda,因为默认不支持移动/拷贝构造(尤其带捕获的 lambda)。得包装成可调用对象类型,比如 std::function<void></void>,再塞进去。
但注意:std::priority_queue 要求元素可比较,而 std::function 没有定义 operator。所以必须自定义比较器,且比较逻辑得落在「优先级」上,不是落在函数本身。
- 推荐把任务封装成结构体,含
priority(int 或 timestamp)、callback(std::function<void></void>)字段 - 比较器只比
priority:小顶堆用greater,大顶堆用less;调度器通常要取最高优先级,所以用大顶堆更自然 - 别把时间戳直接当 priority 用——如果用
std::chrono::steady_clock::now().time_since_epoch().count(),数值越来越大,但你要的是“越早该执行越靠前”,得取负或反转比较逻辑
如何让任务带延迟执行(非立即触发)
单纯靠 priority_queue 只能排序,不能阻塞等待。真要延迟,得配合线程 + 条件变量 + 系统时钟。
核心思路是:每次取堆顶任务时,检查它的计划执行时间是否已到;没到就 wait_until,到了再执行。
立即学习“C++免费学习笔记(深入)”;
- 任务结构里必须存
execute_at(std::chrono::steady_clock::time_point),不是 delay 毫秒数 - 调度线程循环中用
cv.wait_until(lock, next_time_point),而不是sleep_for——避免被唤醒后错过执行时机 - 每次 push 新任务后,要 notify_one:因为可能新任务比当前等待的任务更早,需要重算等待点
- 别在锁内执行 callback:防止阻塞整个调度队列,执行前先 unlock
std::priority_queue 的线程安全边界在哪
它本身完全不线程安全。所有操作——push、top、pop——都必须加锁保护。
但锁粒度很重要:锁整个调度循环?还是只锁队列操作?后者更合理,否则 callback 执行时间长会卡住后续任务入队。
- 用
std::mutex保护std::priority_queue实例,所有访问都加std::lock_guard - 不要把
pop和 callback 执行放在同一把锁里;典型模式是:锁中top→ 记录任务 →pop→ 解锁 → 执行 callback - 注意
empty()和top()之间存在竞态:必须在锁内连续判断,不能先empty再top - 如果任务数量极大(万级/秒),
priority_queue的O(log n)插入没问题,但频繁锁争用会成瓶颈;这时得考虑无锁队列或分片设计
为什么不用 std::set 替代 std::priority_queue
因为 std::set 支持迭代器和随机 erase,看起来更灵活。但调度器不需要这些——你只关心「取最高优先级+删掉它」,priority_queue 更轻量、缓存友好、无额外指针开销。
-
std::priority_queue是 heap,底层是 vector,内存连续;std::set是红黑树,节点分散,cache miss 高 - 如果你需要「取消某个已入队但未执行的任务」,
priority_queue确实做不到——这时才考虑std::set或带 handle 的第三方堆(如boost::heap::fibonacci_heap) - 别为了“未来可能要取消任务”提前上复杂方案。先用
priority_queue+ 原子标志位(callback 开头检查是否已取消)更简单可靠
wait_until 的实际唤醒时间受系统调度影响,没法保证绝对准时。










