--- tags: [] date created: 星期四, 十一月 20日 2025, 11:58:41 晚上 date modified: 星期四, 十一月 20日 2025, 11:59:25 晚上 --- # 2.3.1 事件总线架构与路由机制 (Event Bus Architecture & Routing Mechanism) ## 一、 约束输入与对齐 (Constraints & Alignment) 基于您提供的设计文档(特别是 `05_任务调度器设计.md`)和系统环境,我们面临以下**硬性约束**: 1. **进程内通信 (In-Process)**:本节讨论的是同一个进程(`main_app`)内部,不同 C++ 对象(模块)之间的交互。**严禁**引入 Socket、Pipe 或由 OS 调度的 IPC 机制(如 DBus/ZMQ),以避免微秒级的系统调用开销。 2. **语言标准**:必须兼容 **C++14** (GCC 7.3)。 3. **实时性要求**:控制指令(如 `StopModule`)必须在 **\< 1ms** 内到达目标模块。 4. **全链路追踪**:事件总线是 `TraceID` 传递的关键载体,必须支持上下文的自动传播。 ----- ## 二、 权衡分析与选项呈现 (Trade-off Matrix) ### 议题 1:路由分发策略 (Dispatch Strategy) | 选项 | A. 纯同步直接调用 (Synchronous Direct) | B. 纯异步队列 (Asynchronous Queued) | C. 混合双通道 (Hybrid Dual-Channel) **(推荐)** | | :--- | :--- | :--- | :--- | | **机制** | `publish()` 时直接在**调用者线程**遍历并执行所有回调函数。 | `publish()` 将事件推入队列。后台线程池异步取出并执行回调。 | 提供 `publishSync`(高优指令)和 `publishAsync`(状态上报)两个接口。 | | **延迟** | **最低 (微秒级)**。无上下文切换,无排队。 | **较高**。受队列深度和调度器负载影响。 | **灵活**。关键指令零延迟,非关键消息不阻塞主业务。 | | **死锁风险** | **高**。如果回调函数里又发了新事件,容易导致递归死锁。 | **低**。解耦了生产者和消费者。 | **中**。需规范同步通道的使用场景。 | | **适用场景** | 紧急停止、资源抢占。 | 日志上报、非关键状态更新。 | **生产环境标准解**。 | ### 议题 2:订阅者模型 (Subscriber Model) | 选项 | A. 泛型/模板回调 (Type-Erasure) **(推荐)** | B. 继承接口 (Inheritance) | | :--- | :--- | :--- | | **机制** | `bus->subscribe(lambda)`。利用 `std::function` 和 `std::type_index`。 | 订阅者必须实现 `IEventHandler` 接口。 | | **耦合度** | **极低**。模块不需要继承特定基类,只要函数签名对就行。 | **高**。侵入性强,增加类层级复杂度。 | | **灵活性** | **高**。支持 Lambda,便于捕获 `this` 指针或上下文。 | 低。 | | **性能** | 极高(现代编译器优化 `std::function` 很好)。 | 虚函数调用开销(微小)。 | ----- ## 三、 基线确立与实施规范 为了兼顾雷达系统对**指令的即时响应**(如资源抢占)和**状态处理的高吞吐**(如海量模块状态变更),我们确立 **C. 混合双通道 + 泛型回调** 为设计基线。 ### 1\. 接口定义基线 (C++14) 我们定义一个强类型的、支持 `TraceID` 注入的接口。 ```cpp class IEventBus { public: virtual ~IEventBus() = default; /** * @brief 订阅特定类型的事件 * @tparam EventType 事件结构体类型 * @param handler 回调函数,接收 const EventType& */ template void subscribe(std::function handler); /** * @brief 同步发布 (高优先级指令) * @details 在当前线程立即执行所有订阅者。调用者会被阻塞直到所有处理完成。 * @param event 事件对象 (需继承自 BaseEvent 以携带 TraceID) */ template void publishSync(const EventType& event); /** * @brief 异步发布 (状态上报/非关键消息) * @details 将事件放入无锁队列,由 EventBus 内部的 Worker 线程稍后处理。立即返回。 * @param event 事件对象 */ template void publishAsync(const EventType& event); }; ``` ### 2\. 核心实现机制 - **同步通道 (`publishSync`)**: - **实现**:直接查找 `std::unordered_map>`。 - **锁策略**:使用 `std::shared_timed_mutex` (读写锁)。发布时加**读锁**(允许多个事件同时发布,只要不修改订阅关系),订阅时加**写锁**。 - **死锁规避**:**严禁**在 `publishSync` 的回调中再次调用 `subscribe`(修改订阅表)。允许递归调用 `publish`,但需注意栈溢出风险。 - **异步通道 (`publishAsync`)**: - **实现**:维护一个 `WorkQueue`。由于事件类型各异,队列元素需使用 `std::function` 包装器(Type Erasure)来存储“执行动作”,而不是存储原始事件数据。 - **并发模型**: - **单分发线程 (默认)**:一个后台线程专门负责从队列取任务并执行。保证了同一事件的消费顺序。 - **队列选型**:**MoodyCamel `ConcurrentQueue`** (MPMC 无锁队列) 或 `boost::lockfree::queue`。鉴于麒麟系统库现状,若无第三方库,使用 `std::deque` + `std::mutex` + `std::condition_variable` 也是可接受的(吞吐量在控制面不是瓶颈)。 ### 3\. TraceID 的隐式传递 (2.3.2 预埋) - **基线要求**:`IEventBus` 不仅仅是搬运工,它还是**上下文的管理者**。 - **同步发布时**:`TraceID` 自然随着线程栈传递。 - **异步发布时**:`publishAsync` 必须在**入队时**捕获当前线程的 `TraceID`,并将其打包到 Lambda 中。在**出队执行时**,先恢复该 `TraceID` 到线程本地存储 (TLS),再执行回调。 - *这解决了异步调用导致追踪链断裂的经典难题。* ### 4\. 异常安全边界 - **基线决策**:`EventBus` **必须捕获**订阅者抛出的所有异常。 - **论证**:一个模块的回调函数崩溃(throw exception)绝不能导致发消息的模块(如调度器)崩溃,也不能中断后续其他模块接收该事件。 - **行为**:`try-catch` 包裹每个 handler 的调用。捕获异常后,记录 `ERROR` 日志(包含 TraceID),然后继续执行下一个 handler。 ----- ## 总结与下一步行动 我们确立了 **2.3.1 事件总线** 的基线: 1. **架构**:**混合双通道 (Sync/Async)**。 2. **实现**:**泛型 Pub/Sub**,基于 C++14。 3. **安全**:**异常隔离** + **读写锁保护**。 有了这个“神经系统”,我们可以开始讨论在这上面跑什么“数据包”了。为了让全链路追踪真正生效,我们需要定义事件的“信封”格式。 **下一步建议**:进入 **2.3.2 全链路追踪上下文传递 (Trace Context Propagation)**,详细定义 `BaseEvent` 结构和 `TraceContextGuard` 的实现机制。这是之前多次提到的 `TraceID` 落地的具体技术点。 **提问**:您是否确认 **“混合双通道 + 泛型回调”** 的事件总线基线?确认后我们将深入 2.3.2。