让控制命令优先
学习目标
在同一条机器人流水线中,让紧急 ControlCommand 比普通帧分析更早排队,同时保留 future 的结果和异常观察路径。
场景问题
传感器帧解析可以稍后开始,但一条停止或限速命令不应被普通分析任务长期挡在队列后面。
推荐方案
使用 submit_priority(priority, task)。优先级从 0(低)到 3(关键);普通任务使用默认优先级。下面的示例以单工作线程演示两类工作均可正确完成。
#include <iostream>
#include <string>
#include <executor/executor.hpp>
int main() {
executor::Executor executor;
executor::ExecutorConfig config;
config.min_threads = 1;
config.max_threads = 1;
if (!executor.initialize_ex(config)) {
return 1;
}
auto analysis = executor.submit_priority(0, [] { return std::string{"analysis"}; });
auto control = executor.submit_priority(2, [] { return std::string{"control"}; });
const auto analysis_result = analysis.get();
const auto control_result = control.get();
std::cout << "priority tasks=" << analysis_result << "," << control_result << '\n';
executor.shutdown();
return analysis_result == "analysis" && control_result == "control" ? 0 : 1;
}完整源码:examples/tutorial/02_priority.cpp。
./build/examples/tutorial/tutorial_02_priority预期输出
priority tasks=analysis,control为什么这样做
submit_priority()仍返回future;调用get()可获得结果,也可重新抛出任务异常。- 优先级只影响等待队列的取出顺序;已经开始运行的低优先级任务不会被抢占。
- 多工作线程、已有执行中的任务和同优先级 FIFO 都会影响实际观察到的完成顺序,因此不要把这个示例的输出顺序当作调度时序证明。
优先级任务如何接收输入
完整形式是 submit_priority(priority, fn, args...):只有最前面的 priority 是额外调度信息,后面的 callable 与参数保存规则和 submit(fn, args...) 相同。
ControlCommand command = read_command();
auto applied = executor.submit_priority(
3, apply_control_command, command);command 会按值保存到任务中。若改用 lambda,也优先按值捕获命令或捕获稳定 owner;提高优先级不会缩短引用必须存活的时间,也不会让裸 this、指针或共享可变状态变安全。
运行假设与所有权
示例固定一个 worker,只提交一个分析任务和一个控制任务,目的是验证两种 future 都能完成,而不是证明控制任务一定先完成。任务返回短字符串,没有捕获外部对象;真实 ControlCommand 应按值或用明确所有权传入,不能引用提交函数栈上的临时对象。
优先级整数最终映射到四档:priority <= 0 为 LOW,1 为 NORMAL,2 为 HIGH,priority >= 3 为 CRITICAL。应用应定义一张全局等级表,不要让不同模块用任意大数互相“加价”。如果最高级任务长期占据大多数提交,普通任务可能持续饥饿。
失败如何观察
- 空任务或关闭后提交会形成可观察的拒绝/异常结果;不要忽略返回的
future。 - 控制任务本身失败时,在其
future.get()处处理;长期运行的统一失败统计将在可靠性专题展开。
故障注入与退出
按以下方式验证设计,而不是只看一次正常输出:
- 先提交一个会阻塞 worker 的 LOW 任务,再提交 CRITICAL 任务;确认后者不能抢占已经运行的工作。
- 让控制任务抛异常;确认它只在自己的 future 和失败状态中出现,不会被分析任务的成功掩盖。
- 用小队列持续提交最高级任务;观察拒绝、queued 数和普通任务等待时间。
- 关闭时先停止控制与分析生产者,再消费仍持有的 futures,最后有界排空 Executor。
若业务要求“控制命令在 5 ms 内生效”,需要测量排队时延并设计超时/降级;把等级改成 CRITICAL 不是验收标准。
需求变化时如何演进
| 新需求 | 下一步选择 |
|---|---|
| 只要紧急工作更早排队 | 保持 submit_priority(),监控各等级等待时间 |
| 必须在地图加载后执行 | 使用任务依赖,优先级不承担正确性 |
| 命令旧了就不能执行 | 在任务内校验 deadline/sequence,并设计拒绝结果 |
| 有固定周期和 jitter 预算 | 迁移到专用实时任务 |
| 控制输入持续流入 | 用有界通信组件表达背压,不用无限提交任务 |
何时不选
需要确定的周期、抖动预算、CPU 亲和性或运行中抢占时,不要提高普通线程池优先级来模拟实时性;请进入专用实时线程教程。
下一步阅读
延迟重试与健康检查处理“现在不做”和“定期做”两种新问题。