子goroutine panic不会传播到主goroutine,因recover仅作用于当前goroutine的defer函数中;必须在每个子goroutine内显式用defer+recover捕获并清理资源。

子 goroutine 的 panic 不会传播到外层,但也不会自动被隔离——不加 recover 就直接崩溃退出,还可能漏掉资源清理。必须每个要保护的 goroutine 内部显式加 defer + recover。
为什么主 goroutine 的 recover 捕获不到子 goroutine panic
Go 的每个 goroutine 有独立调用栈和 panic 栈。recover 只作用于当前 goroutine,且仅在 defer 函数中生效。你在 main 里写 defer func() { recover() }(),然后 go f() 里 panic,recover 根本不会被触发。
- 现象:程序直接 panic exit,日志里看不到任何 recover 输出
- 根本原因:
recover不是全局监听器,它只“守”自己那条 goroutine 的最后一道门 - 后果:子 goroutine panic 后静默退出,
file.Close()、conn.Close()、close(ch)等都可能被跳过
goroutine 内部 recover 的标准写法
必须满足三个条件:在 goroutine 内部、defer 包裹、recover 在 defer 函数体里调用。顺序不能错,panic 发生前就得注册好 defer。
-
defer要在可能 panic 的代码之前声明(语法上靠前) - 推荐用匿名函数封装,避免逻辑泄漏到外部作用域
- recover 后建议记录日志,至少包含 goroutine 标识(比如传入 ID 或用
runtime.GoID())
go func(id int) {
defer func() {
if r := recover(); r != nil {
log.Printf("goroutine %d panicked: %v", id, r)
// 这里可做清理:close(ch), db.Close(), etc.
}
}()
// 可能 panic 的业务逻辑
_ = []int{1}[5] // index out of range
}(123)recover 后要不要重启 goroutine
不是所有 panic 都适合 recover 后继续跑。recover 只是恢复控制流,不代表状态一致。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- 简单任务(如单次 HTTP 请求)recover 后直接返回即可,不用重启
- 长周期 worker(如从 channel 持续取任务)recover 后可选择循环重入,但要确保上下文已重置(比如清空局部 map、重置计数器)
- 避免在 defer 中再 panic —— 这会让 recover 失效,且无法被再次捕获
- 对不可恢复错误(如
nil pointer dereference导致内存损坏风险),recover 后应主动os.Exit(1)或上报告警,而不是硬扛
用 channel + error 替代纯 recover 做错误通知
recover 解决的是“不崩溃”,但不解决“怎么知道它错了”。尤其多个 goroutine 并行时,光靠日志很难关联上下文。
- 把错误结果通过
chan error或结构体通道(如chan Result)传出,主 goroutine 统一处理 - 通道必须带缓冲,或确保接收方已就绪,否则 goroutine 会卡在发送上,变成泄露
- 不要用全局变量或共享 map 存 error —— 竞态难查,且无法绑定 goroutine 生命周期
type Result struct {
ID int
Err error
}
ch := make(chan Result, 10)
go func(id int) {
defer func() {
if r := recover(); r != nil {
ch <- Result{ID: id, Err: fmt.Errorf("panic: %v", r)}
}
}()
// 业务逻辑...
ch <- Result{ID: id, Err: nil}
}(456)真正容易被忽略的点是:recover 不是兜底开关,它是精细手术刀。每个需要长期存活或持有资源的 goroutine,都得自己配一把——少一个,就可能漏掉一次 close、一次 flush、一次 context cancel。

















