Executor 是什么
Executor 是一个面向 C++20 应用的进程内并发执行基础设施库。它通过统一 Facade 管理普通异步任务、低延迟队列、周期实时线程、长期 Blocking I/O 和可选 GPU 工作,并提供有界通信、任务编排、背压与生命周期诊断。
大多数用户只需要从 submit_auto() 开始;只有遇到明确的周期、容量、I/O 或数据传递约束时,才需要进入专用路径。
对使用者而言,重点不是先理解线程池内部实现,而是先回答三个问题:
- 这段工作是一次性后台计算、软周期维护,还是有 jitter 预算的周期控制?
- 调用方需要结果、逐项完成确认,还是只需要服务级失败告警?
- 数据应该随任务参数传入,还是需要在长期运行的线程之间持续传递?
这些答案决定是否继续使用默认任务路径,还是进入实时、通信、GPU 或长期 worker 专题。Executor 提供这些能力,但不会替应用替代业务建模。
先判断它是否适合你的项目
适合使用 Executor
- 应用中有多处短时后台工作,不希望每个模块各自创建和销毁线程。
- 任务需要返回值,并希望在调用线程通过
std::future观察异常。 - 需要优先级、延迟、软周期、批量或依赖编排,但不想直接维护调度线程。
- 既有普通并发任务,也有 CAN、传感器采集或控制循环等专用周期线程。
- 长期运行服务需要统一查看提交拒绝、任务异常、等待超时和实时队列丢弃。
- GPU 是可选加速路径,并且无后端或无设备时必须安全诊断或降级。
不应该把它当作什么
- 不是协程运行时:API 以普通可调用对象和
std::future为核心,不提供 coroutine scheduler。 - 不是分布式消息系统或数据流框架:
Topic只负责进程内扇出,不提供网络传输、持久化、重放或确认;任务依赖能表达完成顺序,但不替你设计消息 schema 或数据所有权。 - 不是硬实时操作系统:专用实时线程能减少通用线程池带来的不确定性,但最终 jitter 仍取决于任务体、操作系统、权限、CPU 隔离、内存驻留和目标硬件。
- 不能安全强制终止任意正在运行的 C++ 函数:长期工作必须主动响应 stop 请求或 deadline;业务函数仍应有界、可结束。
submit_periodic()是普通线程池上的软周期任务:不等同于专用实时线程。- 不是“开启优先级就实时”:
submit_priority()只影响普通任务的排队顺序,不提供 deadline 保证,也不能抢占已经运行的任务。
如果你只有一两个生命周期清晰的长期线程,线程之间也没有共享调度、诊断或动态任务需求,直接使用 std::jthread 可能更简单。引入库应减少系统责任,而不是只为了隐藏一次 std::thread 创建。
先从默认 Facade 开始
| 用户需求 | 默认入口 | 调用方得到什么 | 何时进一步了解 |
|---|---|---|---|
| 一次性后台计算 | submit_auto(lambda) | future 中的返回值或异常 | 需要 priority、delay、batch、dependency 时 |
| 独立 CPU/GPU 实现 | cpu_gpu_task() + submit_auto() | 已选路径的 future 完成或异常 | 需要 GPU 注册、诊断或调参时 |
| 有界低延迟或实时投递 | dispatch_auto() | 队列是否接收,不是完成 | 需要容量、drop、周期与性能细节时 |
| 长期可中断 I/O | start_worker() | worker 启动结果和生命周期 | 需要协议、wakeup 与部署细节时 |
| 跨线程传递数据 | executor::comm | FIFO、最新值、快照或阶段同步 | 需要按数据语义选择组件时 |
默认 Auto 不会为了性能偷偷选择无锁、实时或 GPU 后端。GPU 也不是普通任务变慢后的自动答案:只有业务明确拥有独立 CPU/GPU 实现、数据规模足以覆盖传输与启动成本,且部署环境可诊断时才应进入该路径。
用一张图理解执行路径
业务请求
├─ 默认提交一次工作 ──> submit_auto ──> 默认异步 ──> future / routing decision
├─ 明确有界投递约束 ──> dispatch_auto ──> 指定后端 ──> admission / status
├─ 管理长期可中断循环 ──> start_worker ──> 专属 worker ──> lifecycle handle
└─ 在线程间传值 ──────> executor::comm ──> channel / mailbox / snapshot / phase大多数新用户只需要第一行。先通过 Executor::instance() 和 submit_auto() 完成一个真实业务任务;出现明确的周期、背压、长期 I/O 或数据所有权问题后,再进入对应专题。不要因为库提供底层 Manager、无锁队列或 GPU executor,就从这些类型开始集成。
一次普通任务发生了什么
auto& executor = executor::Executor::instance();
auto result = executor.submit_auto([] { return parse_frame(); });
try {
consume(result.get());
} catch (const std::exception& error) {
report(error);
}调用 submit_auto(lambda) 后,工作进入默认异步执行器;worker 执行可调用对象,返回值或异常被写入 future。调用方在 get() 处重新取得结果或异常,因此“提交成功”与“任务执行成功”是两个不同阶段。submit() 仍是显式默认线程池入口,适合兼容代码或需要直接表达该语义的专家场景。
你仍然负责:
- 确保任务捕获对象在执行期间有效,优先按值捕获短小输入。
- 决定在哪里调用
get(),避免在互相依赖的任务中制造线程池饥饿。 - 让任务有界完成,不在共享 worker 中运行无法停止的永久循环。
- 为 fire-and-forget 工作配置 callback、状态或业务事件,不能让失败无观察者。
- 在进程或组件边界决定是等待已接收任务,还是快速停止。
能力边界比功能列表更重要
排队顺序不等于完成顺序
高优先级任务可以更早从等待队列中被选择,但多个 worker 会并发运行,先开始的任务也可能更晚完成。若业务要求“加载完成后才能规划”,应使用任务依赖或在调用方显式等待,而不是依靠优先级碰巧形成顺序。
超时不等于强制中断业务代码
等待超时首先表示调用方没有在预算内观察到完成。C++ 不能安全地从外部终止任意正在执行的函数,因此任务本身仍应支持业务级截止时间、停止标志或可取消 I/O。
实时能力来自整条部署链路
专用周期线程只是基础。周期回调长度、单周期消费预算、队列容量、内存分配、CPU 亲和性、实时优先级和平台权限共同决定结果。Linux 调优失败可能安全回退;Windows 的调度语义不同;Android 普通 App 默认不自动申请 SCHED_FIFO,affinity / mlock / timer slack 均为 best-effort。上线前必须读取实际状态字段,而不是只检查 start 返回成功。
“同步无锁”不等于整条路径无锁
自 0.4.0 起,通信组件为关键同步路径提供固定存储和原子实现,但“同步无锁”不覆盖 payload 操作、callback、缺页或 OS 调度。Topic<T> 属于普通控制面,不是实时原语。精确保证见 0.4.0 迁移说明。
第一次接入的推荐顺序
- 用默认配置运行第一个任务,确认构建、链接、默认路由、返回值和异常路径。
- 在真实组件边界确定初始化与关闭的 owner。
- 先阅读执行模型与路由边界,再按自然语言需求阅读如何选择提交接口,不要从 API 名称反推设计。
- 现有项目先按从线程代码迁移划清所有权,再用生产接入检查清单补齐有界等待与失败观察。
- 只有需求明确时,再进入实时与通信、GPU 或高级接口。
如果目前只想验证项目能否工作,下一页直接进入构建与安装。