不能直接 defer recover(),因为 recover() 只对当前 goroutine 有效,而订阅者回调常在独立 goroutine 中执行,发布端的 defer 无法捕获其 panic;必须在每个 handler 执行前用 safecall 封装 recover 逻辑,确保单个 handler panic 不影响其他 handler,且 recover 后可继续分发,但需注意状态副作用不可回滚。

为什么在事件分发中不能直接 defer recover()?
因为 Go 的 recover() 只对当前 goroutine 有效,而发布订阅模式里,订阅者回调通常由独立 goroutine 执行(比如用 go handler(event)),此时在发布端 defer recover() 完全捕获不到订阅者内部 panic。你得把 recover() 放到每个 handler 的执行边界里。
如何安全调用单个订阅者函数
每个 handler 封装一层带 recover 的执行逻辑,避免一个 panic 导致整个事件分发中断:
func safeCall(handler func(Event), event Event) {
defer func() {
if r := recover(); r != nil {
log.Printf("handler panicked: %v", r)
// 可选:上报指标、记录 traceID、触发告警
}
}()
handler(event)
}
调用时:safeCall(sub.Handler, event)。注意不要在 safeCall 外再套 goroutine —— 否则 recover 又失效了。
在并发分发中怎么避免 panic 波及其他 handler
如果你用 go safeCall(h, e) 并发执行多个 handler,每个 goroutine 都有自己的 panic/recover 边界,互不影响。但要注意:
- 所有 handler 共享的资源(如全局 map、日志缓冲区)仍需加锁或用 sync.Pool,panic 不会自动释放锁
- 如果 handler 内部启动了子 goroutine 且未处理 panic,那些子 goroutine 的 panic 依然逃逸
- 别在 recover 后继续往 channel 发送事件 —— 可能已关闭,引发新 panic
recover 之后还能继续分发吗?
可以,而且应该。事件分发的核心契约是「一个 handler 失败,不影响其余 handler」。只要你在每个 handler 调用点都包了 safeCall,panic 就被拦截在最小作用域内。真正要警惕的是:recover() 不能修复程序状态,比如 handler 中已修改了共享结构体字段但中途 panic,这部分副作用无法回滚 —— 这类逻辑应设计为幂等或使用事务型操作(如 CAS 更新)。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











