
defer + recover 仅能捕获当前协程内发生的panic;在主协程中定义的recover无法拦截子协程触发的panic,这是由Go的协程隔离机制决定的核心行为。
`defer` + `recover` 仅能捕获**当前协程内**发生的panic;在主协程中定义的recover无法拦截子协程触发的panic,这是由go的协程隔离机制决定的核心行为。
在Go语言中,defer 和 recover 构成了一套协程局部(goroutine-local) 的错误恢复机制:recover() 只能在 defer 函数中调用,且仅对同一协程内由 panic() 触发的异常生效。一旦 panic 发生在另一个 goroutine 中,主协程的 recover 完全不可见——这正是两个版本输出差异的根本原因。
? 版本1为何能“捕获”panic?
func b() {
go fmt.Println([]string{}[2]) // ❌ panic 在 main goroutine 中立即发生!
}
关键点在于:[]string{}[2] 是函数调用的实参表达式,必须在 go fmt.Println(...) 启动新协程之前求值。因此,索引越界 panic 实际发生在 调用 b() 的主协outine(即 a() 所在协程)中,此时 a() 的 defer 尚未返回,recover() 成功捕获并打印 "runtime error: index out of range",程序继续执行。
⚠️ 版本2为何崩溃?
func b() {
go func() { // ✅ 新协程启动后才执行此匿名函数
fmt.Println([]string{}[2]) // ❌ panic 发生在新 goroutine 内部
}()
}
此处 go 后跟的是一个无参闭包,参数列表为空,因此 go 语句本身可立即完成调度。真正的 []string{}[2] 计算和 panic 被推迟到新协程启动后执行。由于该协程内没有 defer/recover,panic 逃逸至默认处理器,导致进程终止并输出完整堆栈(含 goroutine 5 [running])。
✅ 正确处理子协程panic的方式
若需捕获子协程 panic,必须在该协程内部设置 defer/recover:
func b() {
go func() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Recovered in goroutine: %v\n", r) // 输出: Recovered in goroutine: runtime error: index out of range
}
}()
fmt.Println([]string{}[2])
}()
}
? 注意:recover() 不会跨协程传播错误。如需将子协程错误通知主协程,应使用 channel、sync.ErrGroup 或 context 等显式通信机制,而非依赖 recover。
? 总结要点
- recover() 作用域严格限定于当前 goroutine;
- 函数/协程调用的参数表达式总在调用前求值(版本1 panic 在主协程);
- go f() 中若 f 是闭包,其函数体在新协程中执行,panic 也属于该协程;
- 协程间 panic 隔离是 Go 的明确设计,保障了并发安全性与错误边界清晰性;
- 生产环境应避免依赖 recover 处理业务逻辑错误,优先使用返回错误值(error)和结构化错误处理。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











