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 能解决的。










