go无内置Observer接口,需手动实现:用map存带唯一ID的回调函数、sync.RWMutex保障并发安全;避免直接用map[Interface{}]func();异步通知应启用goroutine并加超时或非阻塞发送以防阻塞。

Go 里没有内置的 Observer 接口,得自己搭骨架
Go 语言不提供类似 java 的 java.util.Observer 或 C# 的 Event 关键字,所有观察者逻辑必须手动组合。核心就三样:map 存订阅者、sync.RWMutex 保并发安全、用函数类型当回调。别试图找“标准库实现”,它不存在。
常见错误是直接用 map[interface{}]func() 存 handler,结果发现无法比较函数值、不能删除特定订阅、也不支持泛型事件类型。正确做法是给每个订阅分配唯一 id(比如 int64 或 uuid.UUID),再用 map[int64]func(interface{}) 管理。
用 channel 实现异步通知时要注意阻塞和泄漏
有人喜欢让每个 observer 拿一个 chan interface{},发布者往所有 channel 发送事件。这在高并发或慢消费者场景下极易卡死:一旦某个 channel 没人收,publish 就会永久阻塞。
更稳妥的做法是把通知逻辑放到 goroutine 里,并加超时或非阻塞发送:
立即学习“go语言免费学习笔记(深入)”;
go func(handler func(interface{})) { select { case <-time.After(5 * time.Second): // 超时丢弃,避免堆积 default: handler(event) } }(h)
还要记得在取消订阅时关闭对应 channel(如果用了 channel),否则可能引发 goroutine 泄漏。
泛型版 EventBus 需要按事件类型分发,不能只靠 interface{}
用 interface{} 做事件载体看似灵活,实际会导致运行时类型断言失败、无法静态检查、ide 失去提示。Go 1.18+ 推荐用泛型约束事件类型:
type Event interface{ ~string | ~int | MyCustomEvent } type EventBus[T Event] struct { handlers map[int64]func(T) mu sync.RWMutex nextID int64 }
func (e *EventBus[T]) Subscribe(handler func(T)) int64 { e.mu.Lock() defer e.mu.Unlock() id := atomic.AddInt64(&e.nextID, 1) e.handlers[id] = handler return id }
func (e *EventBus[T]) Publish(event T) { e.mu.RLock() defer e.mu.RUnlock() for _, h := range e.handlers { h(event) } }
这样调用时就能明确约束事件类型:bus := &EventBus[UserCreated]{} ,编译器会帮你拦住传错类型的 bug。
取消订阅必须支持精确移除,不能只靠 defer 或全局清理
很多示例用 defer unsubscribe(),但真实服务中 observer 生命周期往往跨 http 请求、gRPC 流或长连接,没法靠 defer 自动触发。必须提供显式 Unsubscribe(id int64) 方法,并确保线程安全。
容易忽略的点:
- 多次调用
Unsubscribe同一个id应静默成功(幂等) - 遍历时不能直接从
map删除(Go runtime panic),要用两阶段:先收集 key,再循环删除 - 若用
sync.Map替代map + mutex,注意LoadAndDelete是原子的,但遍历仍需额外同步
真正难的不是写通逻辑,而是让取消行为在分布式、重连、超时、panic 恢复等各种边界下依然可靠 —— 这需要结合 context、心跳检测和持久化订阅状态,不是单个 EventBus 能解决的。