recover只能在当前goroutine中捕获panic,无法跨goroutine生效;main中defer recover()对流水线各阶段的子goroutine panic完全无效,因每个go func()启动的worker是独立goroutine,panic会静默终止导致任务丢失、channel阻塞或资源泄漏。

recover 在 Go 并发流水线中不能跨 goroutine 生效,必须为每个阶段单独配置 defer func() { recover() }(),否则 panic 会静默终止该 worker,导致任务丢失、channel 阻塞或资源泄漏。
为什么 main 里 defer recover() 对流水线没用
流水线各阶段通常由独立 go func() { ... }() 启动,它们是隔离的 goroutine。主线程的 recover() 只作用于自身调用栈,对子 goroutine 的 panic 完全无感——这不是 bug,是 Go 运行时的明确设计。
常见错误现象:
- 某个解析 worker panic 后消失,上游还在往输入 channel 发数据,最终阻塞在
ch - 下游
for range outputCh永远等不到 close,协程卡死 - 文件句柄、数据库连接未关闭,出现泄漏
根本原因:goroutine 是调度单元,panic 传播范围仅限本 goroutine;recover() 的 argp 校验机制要求它必须在与 panic 相同的栈帧中执行。
立即学习“go语言免费学习笔记(深入)”;
每个流水线阶段都得自己加 defer recover
不能偷懒写一个“全局 recover 函数”然后到处 defer handlePanic()——除非你给它加上 //go:noinline 注释,否则编译器内联后 recover() 会返回 nil。
正确写法(以扇出 worker 为例):
func parseWorker(id int, in <-chan string, out chan<- []string, wg *sync.WaitGroup) {
defer wg.Done()
defer func() {
if r := recover(); r != nil {
log.Printf("parseWorker %d panicked: %v", id, r)
// 注意:此处不 close(out),由主流程统一控制
}
}()
for line := range in {
words := strings.Fields(line)
out <- words
}
}关键点:
-
defer必须写在for range循环之前,确保 panic 发生时它已注册 - 不要在
recover()后继续往out写数据——状态可能已损坏 - 别
close(out):这会让下游提前退出;应由启动该 stage 的主逻辑负责 close - 若需通知错误,用带缓冲的
errCh chan,而非直接 log 或 panic
recover 后如何安全退出并避免状态污染
recover() 不是回滚机制,它只让 goroutine “软着陆”,不会恢复栈、也不会跳回 panic 前的位置。继续执行后续业务代码极易引发二次 panic 或数据竞争。
推荐做法:
- recover 后立即
return,不走任何业务分支 - 资源清理(如
file.Close()、db.Close())放在独立defer中,而非recover()分支里 - 若该 stage 有输出 channel,且你确定不能再产出有效数据,可选:
close(out)——但必须确保所有消费者都已做好处理 closed channel 的准备 - 永远先检查
r != nil,再做类型断言:switch r := r.(type) { case string: ... case error: ... },否则r.(error).Error()会再次 panic
结合 context 和 errCh 实现整条流水线可控退出
单个 stage panic 不应让整条流水线瘫痪,但也不能放任不管。真实项目中建议:
- 每个 stage 接收
ctx context.Context,并在for range in中监听ctx.Done() - 用一个带缓冲的
errCh = make(chan error, 100)收集所有 stage 的 panic 错误 - 主流程用
select等待errCh或全部 stage 完成,收到首个错误即可cancel()全局 context - 避免在
recover()中直接调用log.Fatal或os.Exit——这会杀掉整个进程,不是“隔离”而是“连坐”
最易被忽略的一点:recover 后的 goroutine 虽然没崩溃,但它持有的局部变量(比如部分初始化的 struct、已部分写入的 map、未 flush 的 buffer)很可能处于中间态。继续使用这些值,比不 recover 更危险。


















