Skip to content

源码结构与阅读路线

这页解决什么问题

当你需要判断一个行为究竟来自 Facade、线程池、任务图还是平台层时,不要从所有头文件开始浏览。Executor 的源码按“公开契约、资源拥有者、执行路径、诊断路径”分层;先找到边界,再追一条任务的生命周期,效率最高。

这页描述当前源码组织。include/executor/ 中的 Facade 和类型是用户可依赖入口;src/executor/ 中的调度器、队列和同步实现可能重构,但其外部可观察行为应由测试和状态 API 固定。

四层结构

flowchart TD A[应用<br/>submit / future / status / shutdown] --> B[公开 Facade<br/>include/executor/executor.hpp] B --> C[资源拥有者<br/>executor_manager.cpp] C --> D[ThreadPoolExecutor] C --> E[RealtimeThreadExecutor] C --> F[GPU executor] C --> G[Comm components] D --> H[PriorityScheduler] H --> I[TaskDispatcher] I --> J[WorkerLocalQueue / LockFreeWorkerQueue] J --> K[worker] E --> L[cycle callback + bounded MPSC queue]

每层只应承担自己的责任:

主要责任不应承担
Facade将用户调用转成可观察的 future、handle 和失败事件直接管理 worker 队列或假设某种锁实现
Manager拥有执行器实例、处理单例/独立实例和注册生命周期替用户决定业务重试或数据幂等
AdapterIAsyncExecutor/IRealtimeExecutor 映射到具体实现修改 Facade 的业务语义
Scheduler/queue排队、分发、消费和背压保存跨任务的业务状态
Monitor/diagnostics统计和报告在回调中阻塞或改变任务结果

从 API 反查源码

你看到的行为首先读然后读验证证据
submit_auto(lambda) 返回 futureinclude/executor/executor.hpp 的路由和模板 APIsrc/executor/thread_pool_executor.cppthread_pool.cppFacade/routing、异常和超时测试
依赖任务未执行Facade 的 TaskGraphStatesubmit_after_with_handlesrc/executor/task/task_dependency_manager.cpptests/test_executor_facade*、依赖教程 smoke
优先级没有抢占PriorityScheduler::dequeue()TaskDispatcher::dispatch()、worker loop优先级测试和队列状态
resize 后任务没有丢失ThreadPool::resize_local_queues()TaskDispatcher::dispatch_batch() 回入队分支resize/并发停止测试
实时任务 dropExecutor::push_realtime_task()RealtimeThreadExecutor::push_task_ex()realtime push overflow 测试、状态计数
无锁队列“偶发空/满”src/executor/util/lockfree_queue.hpp调用方的容量和对象池逻辑MPSC benchmark、TSAN/压力测试

源码阅读时先找“谁拥有对象”和“谁能让它退出”:Executor 拥有实例化模式的 Manager,Manager 拥有执行器,Adapter 用 shared_ptr 快照保护 stop/submit 竞争;实时 cycle_manager 则由调用方拥有,Executor 只借用指针。

同步域不是一把全局锁

当前实现有多个相互独立的同步域:

同步域保护对象典型锁/原子设计原因
Facade failure失败计数、ring buffer、callback 快照failure_mutex_callback 在解锁后执行,避免诊断回调重入内部锁
Facade task graph节点状态、dependents 映射task_graph_mutex_ + graph manager shared_mutex状态转移与依赖解析必须原子观察
Manager registryrealtime/GPU 注册表shared_mutex查询多、注册少;Facade 通过 shared_ptr 快照持有对象,裸指针高级接口需自行与 shutdown 串行化
ThreadPool lifecyclestop、total/completed/activemutex_ + atomic counters提交停止边界与等待完成条件分离,避免锁顺序反转
local queuesworker queue vector 的替换local_queues_mutex_ + atomic shared_ptrresize 时旧 vector 由快照延长生命周期
LockFreeQueue slots槽位就绪和回收序列sequences_ acquire/release数据发布与槽位复用需要顺序关系,不用 mutex 串行化生产者

不要把“使用了 atomic”理解成“没有锁”,也不要为了消除一把锁而跨越同步域读写对象。修改源码前先画出:谁写、谁读、对象何时销毁、哪个条件变量负责唤醒。

两条完成不变量

普通执行路径依赖两个计数事实:

flowchart LR A[accepted task] --> B[scheduler 或 worker queue] B --> C[active worker] C --> D[completed 或 failed] E[completion_ready] --> F[scheduler_empty] F --> G[all local queues empty] G --> H[active_threads = 0] H --> I[total_tasks = completed_tasks]

failed_taskscompleted_tasks 的子集,不能再从完成等式中扣除。任务从 scheduler 出队后,如果 worker ID 失效或本地队列满,dispatcher 必须把任务重新放回 scheduler;否则会出现“提交成功但永远没有 future 结果”的丢失窗口。

实时路径的对应不变量是:

flowchart LR A[accepted realtime task] --> B[bounded MPSC queue] B --> C[current cycle consumes it] C --> D[wrapper returned to pool] E[rejected task] --> F[dropped_task_count + reason counter]

停止时先禁止新 producer,再等待已登记 producer 退出,最后 drain 队列;这保证最终 drain 后不会凭空出现一个“已接受但未清理”的 wrapper。

修改源码时的验证顺序

  1. 先写能暴露不变量破坏的最小测试:future 是否就绪、计数是否对账、关闭是否有界。
  2. 再运行对应局部测试:任务图改动看依赖/Facade 测试,队列改动看 MPSC/实时 overflow,resize 改动看 resize 与并发停止。
  3. 用状态 API 和 failure event 验证用户可见路径,而不只断言内部变量。
  4. 最后运行 TSAN 或压力测试;并发代码“本地单次通过”不能证明没有竞态。
  5. 如果改动性能,按性能测量与回归检查保存环境、原始 JSON 和正确性对账。

继续阅读