一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

Golang中panic与recover函数在嵌套协程下的捕获边界

时间:2026-07-01 09:29:52 编辑:袖梨 来源:一聚教程网

recover只能捕获当前goroutine的panic,子协程panic必须在内部用defer recover()处理,否则进程将因未捕获panic而退出;recover后应立即return,不可继续执行原逻辑。

recover 只能捕获当前 goroutine 的 panic

主协程里写的 defer func() { recover() }()go func() { panic("x") }() 完全无效。这不是 bug,是 Go 运行时的明确设计:每个 goroutine 有独立的 panic/recover 作用域,recover() 只能拦截**正在当前 goroutine 中传播的 panic**。子协程 panic 后,runtime 直接终止该 goroutine 并打印 stack trace,主协程既不会中断,也不会感知——除非它自己也 panic 或提前退出。

子协程 panic 不会静默退出,但会终止整个进程

常见误解是“子协程崩溃不影响主线程”,实际上:哪怕主 goroutine 正在 time.Sleep() 或等待 sync.WaitGroup,只要有一个未 recover 的子协程 panic,程序大概率仍会退出。这是因为 runtime 在所有非主 goroutine 崩溃后,若检测到只剩主 goroutine(或主 goroutine 已结束),就会终止进程。现象是控制台打出 panic: runtime error: ... 然后直接退出,WaitGroup.Wait() 根本拦不住。

  • 子协程 panic → 自身终止 + 打印堆栈
  • 主 goroutine 继续运行,但不等于“程序稳定”
  • 若主 goroutine 结束早于子协程恢复,进程立即退出
  • 若子协程 panic 后残留资源(如未关闭的文件、连接),可能引发泄漏

safeGo 封装必须把 recover 写进子协程内部

不能只在外层加一层 defer,必须确保 recover() 是子协程启动后**第一个注册的 defer**,且调用位置在任何可能 panic 的代码之前。以下写法才有效:

func safeGo(fn func()) {    go func() {        defer func() {            if r := recover(); r != nil {                log.Printf("goroutine panic: %v", r)                // 生产环境建议加上 debug.Stack()            }        }()        fn()    }()}

错误示范包括:

立即学习“go语言免费学习笔记(深入)”;

  • defer recover():注册时就执行,返回 nil
  • go func() { recover() }():不在 defer 中,永远不生效
  • panic("x"); defer func() { recover() }():defer 在 panic 之后注册,来不及捕获

recover 后不要继续执行原业务逻辑

recover 的作用是止住 panic 的栈展开,不是“修复错误后继续跑”。一旦 panic 发生,goroutine 的状态很可能已不一致(比如 map 被部分写入、锁未释放、channel 已 close)。此时再调用 doWork() 或重试,极易二次 panic 或产生脏数据。

  • recover 后应立即 return,或转入明确错误分支(如关闭 channel、释放资源)
  • 不要用 err.(error).Error() 强转,panic 参数可能是 string、int 或自定义 struct;安全做法是先类型断言:if err, ok := r.(error); ok { ... },否则用 fmt.Sprint(r)
  • 日志中建议带上 debug.Stack(),而不是 debug.PrintStack()(后者输出到 stdout,不可控)

最常被忽略的点:recover 不是兜底,而是止损。它解决不了“为什么 panic”,只防止“panic 导致进程挂掉”。真正要做的,是定位 panic 源头(比如空指针解引用、map 访问未初始化、切片越界),而不是靠 recover 掩盖问题。

热门栏目