一、模块(Module)间通讯方式简介
最近一大段的时间任务是做模块(Module)间通讯方案设计,未编码。通过调研,做法如下:
观察者模式
实现思路:维护一个回调函数列表(std::function 或函数指针),广播时循环调用。
优点:简单直接,性能高。
缺点:调用链耦合,容易出现回调地狱,模块之间绑定紧密。
信号槽模式(Qt / boost::signals2)
实现思路:维护一个消息队列,广播时把消息写入多个队列,每个订阅模块消费自己的队列。
优点:解耦,异步处理,支持不同消费速度。
缺点:消息需要复制,内存开销大;实现稍复杂。
事件总线()
实现思路:设计一个统一的 事件总线,模块之间只和总线交互。
事件总线可以支持 同步调用(直接回调)或 异步调用(队列/线程池)。
优点:解耦、扩展性好、支持广播与点对点。
缺点:需要设计完整的调度机制。
常见实现思路:
Apollo CyberRT / ROS2 的进程内 PubSub
一些开源库:
| 库名称 | 语言版本 | 特点与优势 | 适用场景 |
| ------------------------- | -------- | ----------------------- | -------------------- |
| **EventBus (gelldur)** | C++11 | 轻量(37 KB)、快速、类型安全 | 简单事件广播模块轻量集成 |
| **eventbus (DevPaul123)** | C++17 支持 | header-only、RAII注销、线程安全 | 模块解耦、现代 C++ 项目 |
| **eventpp** | C++11+ | 同步/异步、线程安全、高性能、灵活 | 复杂事件处理、性能敏感的框架或工具类项目 |
| **PubSubQueue (IPC)** | C++ | 共享内存、高效 IPC (跨进程) | 高实时要求的进程间通信 |
| **D-Bus** | C/C++ | 系统级消息总线、语言绑定丰富、成熟 | 系统服务间通信、跨语言、跨进程的场景 |
建议选择指南
如果你追求极简低耦合且无需异步复杂逻辑:推荐 gelldur/EventBus
若你希望让事件系统更加灵活、安全并融入现代 C++ 机制:推荐 DevPaul123/eventbus
对性能、线程安全、复杂调度要求高:推荐 eventpp
将来可能扩展到跨进程,关注延迟与速度,可考虑 PubSubQueue
二、实战演练
#includeclass Event{public: Event(int e, std::string e_msg): event_(e), event_msg(e_msg) {} private: int event_; std::string event_msg;}; class ModuleBase{public: virtual ~ModuleBase() {} // 必须要有虚析构函数 virtual void init() = 0; virtual void start() = 0; virtual std::string name() = 0; virtual std::string version() = 0; virtual void processEvent() { std::cout << "event ModuleBase" << std::endl; };}; class EventDispatch{public: void SendEvent(ModuleBase *receiver, Event *event) { receiver->processEvent(); }}; class ModuleA : public ModuleBase{ void init() override { std::cout << "init ModuleA" << std::endl; }; void start() override { std::cout << "start ModuleA" << std::endl; }; std::string name() override { return "ModuleA"; }; std::string version() override { return "1.0.0"; }; void processEvent() { std::cout << "event ModuleA" << std::endl; };}; int main(void){ std::cout << "enter main" << std::endl; ModuleBase *module_ = new ModuleA(); EventDispatch *eventDispatch = new EventDispatch(); Event *e = new Event(10000, "test msg"); eventDispatch->SendEvent(module_, e); return 0;}
整了个gitee仓库,来记录这个软件结构,慢慢逐渐完善。
同时把C++语法沉淀一下,还有一些现代C++思想也要学习,如执行器。
https://gitee.com/ideals_and_love/modularization
Module间通讯,很大程度上无法避免接口调用;对接口调用进一步提炼,是数据的收发,高频数据用回调函数的方式来解决,不过此时仍有一定耦合但也能接受,直接开一个线程高频舒心是不被接受的。其他数据收发,如机械臂运动目标点位的下发,这块直接调用接口也能接受,但是如果不想包含对方的头文件呢?如果模块间交互数据改为buf+size,就很C风格,那结构体就是绕不开的,底层核心问题无法避免,最终还是取舍问题。
模块间通讯再加上这次事件的点对点方式,基本可以覆盖进程内模块间通讯需求。
欢迎关注,欢迎留言交流。
往期重构相关: