-
Notifications
You must be signed in to change notification settings - Fork 0
FAQ
A: MCCC 与通用事件库的定位不同,核心差异如下:
| 特性 | MCCC | eventpp / EnTT / sigslot |
|---|---|---|
| 并发模型 | 无锁 MPSC/SPSC | 通常需要外部加锁或仅支持单线程 |
| 优先级准入 | 三级背压 (HIGH/MEDIUM/LOW) | 无 |
| 堆分配 | 零堆分配 (FixedVector/FixedString/FixedFunction) | 依赖 std::function / std::vector |
| 规范合规 | MISRA C++ 导向 | 通用 C++ 风格 |
| 目标场景 | 安全关键嵌入式系统 (工控/机器人/激光雷达) | 通用桌面/游戏/服务端应用 |
| 消息传输 | 值语义环形缓冲 (trivially_copyable) | 指针/引用传递 |
| 编译期配置 | 队列深度/缓存行/单核模式等宏控制 | 运行时配置为主 |
如果项目无实时性要求、无内存约束、无安全合规需求,通用事件库更简单易用。MCCC 专为需要确定性延迟和资源可控的嵌入式场景设计。
A: MCCC 的核心架构是 MPSC (Multi-Producer, Single-Consumer)。单条总线仅允许一个消费者线程调用 ProcessBatch() / ProcessBatchWith()。
如果需要多个消费者,有以下方案:
-
多总线实例: 为每个消费者创建独立的
AsyncBus实例,生产者向多条总线分别发布。
// 消费者 A 的总线
auto& busA = AsyncBus<PayloadA>::Instance();
// 消费者 B 的总线
auto& busB = AsyncBus<PayloadB>::Instance();-
扇出模式: 单一消费者从总线取出消息后,根据类型转发到不同的处理队列。
-
Component 多回调: 注册多个回调 (通过
Subscribe<T>()),在同一消费者线程内分发给不同的处理逻辑。虽然是单线程处理,但逻辑上实现了多消费者的效果。
不建议多线程并发调用 ProcessBatch(),这会导致未定义行为。
A:
| 特性 | Component | StaticComponent |
|---|---|---|
| 订阅方式 | 运行时动态 (SubscribeSafe()) |
编译期静态 (MakeVisitor()) |
| 取消订阅 | 支持 (Unsubscribe()) |
不支持 (编译期绑定) |
| 生命周期安全 |
weak_ptr 自动检测 |
调用者保证 |
| 运行时开销 | shared_mutex 读锁 + 回调查找 | 零开销 (std::visit) |
| 适用场景 | 需要动态增减订阅的组件 | 消息类型在编译期已确定的组件 |
选择指南:
-
如果组件在运行期间需要动态订阅/取消订阅不同类型的消息,或者组件的生命周期不确定 (可能在回调触发前析构),使用 Component +
SubscribeSafe()。 -
如果组件处理的消息类型在编译期已完全确定,且组件生命周期可控,使用 StaticComponent +
MakeVisitor()。性能优势: 19%~42%。
A:
- 通过回调表 (
DispatchTable) 分发消息 - 运行时查找: 根据
variant的index()定位回调列表 - 需要
shared_mutex读锁保护回调表 (因为Subscribe/Unsubscribe可能并发修改) - 支持运行时动态增减回调
- 通过
std::visit+ Visitor 模式分发消息 - 编译期分发: 编译器根据
variant类型生成跳转表 - 无锁: 不依赖回调表,不需要 shared_mutex
- 消息处理逻辑必须在编译期确定
| 场景 | ProcessBatch | ProcessBatchWith | 加速 |
|---|---|---|---|
| MPSC | 160 ns/msg | 129 ns/msg | 19% |
| SPSC | 161 ns/msg | 93 ns/msg | 42% |
建议: 如果不需要运行时动态订阅,优先使用 ProcessBatchWith。
A: MCCC 通过编译期宏适配 MCU 环境。推荐配置:
// CMakeLists.txt 或编译选项
-DMCCC_QUEUE_DEPTH=1024 // 或 4096,根据 SRAM 大小调整
-DMCCC_SINGLE_PRODUCER=1 // 大多数 MCU 场景为 SPSC
-DMCCC_SINGLE_CORE=1 // 单核 MCU
-DMCCC_I_KNOW_SINGLE_CORE_IS_UNSAFE=1 // 必须显式确认
-DMCCC_CACHELINE_SIZE=0 // 无 D-Cache 的 MCU 不需要缓存行对齐
-DSTREAMING_DMA_ALIGNMENT=4 // 自然对齐即可关键注意事项:
-
SRAM 预算:
MCCC_QUEUE_DEPTH * sizeof(Envelope)是主要内存消耗。Envelope 大小取决于PayloadVariant中最大类型的大小。先确认 SRAM 容量再确定队列深度。 -
单核模式:
MCCC_SINGLE_CORE=1使用relaxed内存序 +atomic_signal_fence,消除硬件内存屏障。在单核 MCU 上完全安全 (ISR 与主循环之间的可见性由编译器屏障保证)。 -
编译器要求: 需要 C++17 支持。GCC 7+ / Clang 5+ / ARM Compiler 6+ 均支持。
-
-fno-exceptions -fno-rtti: MCCC 完全支持无异常和无 RTTI 编译,不依赖任何异常或动态类型信息。
-
ISR 中使用: ISR 中可安全调用
PublishFast()(在 SPSC 单核模式下为 wait-free,无 CAS,无系统调用)。不要在 ISR 中调用Publish()(在 FULL_FEATURED 模式下包含时间戳采集和统计更新)。
A: 分操作讨论:
| 操作 | 线程安全性 | 说明 |
|---|---|---|
Publish() / PublishWithPriority() / PublishFast()
|
多线程安全 (lock-free) | 多个线程可并发调用 (MPSC 模式) |
ProcessBatch() / ProcessBatchWith()
|
单线程调用 | 仅允许一个消费者线程调用 |
Subscribe() / Unsubscribe()
|
需要写锁 | 内部使用 shared_mutex,与 ProcessBatch 的读锁互斥 |
GetStatistics() |
多线程安全 | 原子加载,返回快照 |
SetPerformanceMode() |
多线程安全 | 原子 store |
SetErrorCallback() |
非线程安全 | 应在启动阶段设置,不应在运行时修改 |
关键约束:
-
ProcessBatch()/ProcessBatchWith()严禁多线程并发调用。MCCC 是 MPSC 架构,消费端为单线程设计。 -
Subscribe()/Unsubscribe()会获取写锁,会阻塞ProcessBatch()的读锁。频繁调用会影响消费者吞吐量。建议在初始化阶段完成所有订阅。 - 在 SPSC 模式 (
MCCC_SINGLE_PRODUCER=1) 下,Publish()也仅允许单线程调用。
A: 行为取决于性能模式和消息优先级:
队列未物理满之前,背压机制根据水位和优先级决定是否准入:
| 队列水位 | LOW | MEDIUM | HIGH |
|---|---|---|---|
| < 60% | 入队 | 入队 | 入队 |
| 60%~80% | 丢弃 | 入队 | 入队 |
| 80%~99% | 丢弃 | 丢弃 | 入队 |
| >= 99% | 丢弃 | 丢弃 | 大概率丢弃 |
消息被背压丢弃时:
-
Publish()/PublishWithPriority()返回false - 统计计数器
total_backpressure_drops递增 (仅 FULL_FEATURED) - 如果设置了
SetErrorCallback(),错误回调被触发
所有消息直接尝试入队。队列物理满时:
-
Publish()返回false - 消息静默丢弃,无回调通知
auto& bus = AsyncBus<MyPayload>::Instance();
// 设置错误回调,监控丢弃情况
bus.SetErrorCallback([](const char* msg) {
// 记录日志或触发告警
LOG_WARN("Bus: %s", msg);
});
// 定期检查统计
auto stats = bus.GetStatistics();
if (stats.queue_utilization > 0.8) {
// 队列水位过高,考虑:
// 1. 增大 MCCC_QUEUE_DEPTH
// 2. 提高消费者处理速度
// 3. 降低生产者发送频率
}关键消息应使用 MessagePriority::HIGH,确保在背压场景下优先入队。