Topic 设计
基础用法见消息系统里的 Topic、SyncSubscriber、ASyncSubscriber 和 QueuedSubscriber 页面。下文直接说明这套机制为什么会分成现在这几个角色。
Topic 解决什么问题
Topic 的目标不是做一个“什么都能承载”的消息总线,而是在尽量轻的前提下,把同一进程内最常见的几种数据交接方式统一起来:发布者写入,订阅者按自己的方式接收,必要时缓存最近一份数据,必要时再做多发布者保护。它没有引入 Linux 共享内存、进程间同步或复杂的持久队列语义,就是因为这些需求已经超出了这条轻量路径的边界。
Block 的角色
从源码上看,Topic 的中心结构是 Block。里面放着主题当前的数据、最大长度、订阅者链表,以及控制并发访问的状态。默认情况下它优化的是单发布者路径:如果没有显式启用 multi_publisher,内部只用一个原子 busy 状态来做轻量串行化;一旦启用多发布者,才退回到 Mutex。这条边界很关键,因为它说明 Topic 首先服务的是常见的单发布者场景,而不是默认把所有发布都拖进重同步原语。
缓存为什么是可选的
缓存也是同样的思路。Topic 并不会强制持有一份独立副本,而是允许按需启用 cache。不开 cache 时,发布路径只是把当前数据视图挂到 Block 上;开了 cache,才在内部保留一份稳定副本。换句话说,如果上层需要“最近一份稳定数据”的语义,就显式打开缓存;如果只想做一次轻量交接,就没有必要默认承担额外拷贝成本。
订阅者为什么分类型
订阅者类型之所以分成同步、异步、队列和回调四类,也不是为了把接口做花,而是因为它们代表了四种完全不同的消费语义。SyncSubscriber 关心的是“有新数据时唤醒我”;ASyncSubscriber 更接近“我之后再来取最新结果”;QueuedSubscriber 关心的是“请把每次发布都排进队列”;回调订阅则是“发布时立即触发我”。如果把这些语义都压成一个统一订阅者接口,最后势必要么退化成最保守子集,要么把大量分支逻辑塞进运行期。
它为什么不是严格消息队列
从并发角度看,Topic 更像一套“发布时把数据分发到不同消费形态”的框架,而不是严格意义上的消息队列。同步路径里 有 Semaphore,异步路径里有状态块,队列路径里有 LockFreeQueue,回调路径则挂在 LockFreeList 上。正因为如此,它特别适合进程内模块交接、日志分发、状态广播这类问题;但如果你需要的是进程间共享大 payload、明确的 queue-full 策略或者零拷贝共享槽位,就应该转去 LinuxSharedTopic<T>,而不是继续往 Topic 里堆系统级语义。
WaitTopic 和 domain
还有一个容易被忽略的点是 WaitTopic 和 domain。Topic 不是全局平面命名,而是允许按 domain 组织;WaitTopic 的意义也不是“立刻拿到对象”,而是等待某个主题在对应 domain 中出现。这套机制解决的是模块初始化顺序不完全固定时的组织问题。它属于轻量进程内发现与绑定,不是服务注册中心,也不是跨进程目录。
定位
如果要用一句话概括 Topic 的定位,可以这么说:它是一条进程内、语义负担较低、可按消费方式分流的发布订阅路径。它最擅长的是让不同模块在不共享过多生命周期细节的前提下交换数据,而不是一次性把所有消息系统问题都解决掉。