recover不能实现协程安全退出,仅能捕获本goroutine的panic并软着陆;真要任意深度退出须用runtime.Goexit(),它按序执行defer后终止当前goroutine。

recover 不能实现协程安全退出 —— 它只能让当前 goroutine “软着陆”,但不会终止执行流;真要从任意栈深度退出,必须用 runtime.Goexit()。
recover() 在 defer 中调用才有效,且只捕获本 goroutine 的 panic
很多人误以为在主函数里写个 defer func() { recover() }() 就能兜住所有子协程 panic,结果程序照样崩溃。这是因为:recover() 是 goroutine 局部的,它对其他 goroutine 中发生的 panic 完全无感。
- panic 发生在子协程里 → 主 goroutine 的
recover()永远收不到 -
recover()必须写在defer func() { ... }()里,写成defer recover()会在注册时就执行,此时还没 panic,返回nil - 即使 recover 成功,函数后续代码仍会继续执行(除非你手动 return),这不是“退出”,只是“没崩”
想从嵌套函数里立刻退出整个 goroutine?用 runtime.Goexit()
runtime.Goexit() 是 Go 唯一支持从任意调用深度(比如第 7 层函数)直接终止当前 goroutine 的标准方式。它会立即停止执行、按序运行所有已注册的 defer,然后交还调度权。
- 它不触发 panic,因此外层
recover()捕获不到,也不影响其他 goroutine - 不能跨 goroutine 调用:在 A 协程里调用
runtime.Goexit(),只杀 A,对 B、C 无效 - 别在
defer里调用它 —— 会导致 defer 链中断,可能死锁或资源泄漏 - 典型用法:
if shouldExit { runtime.Goexit() },放在深层函数里,比层层 return 干净得多
goroutine 级 panic 防护必须每个都单独加 defer+recover
HTTP handler、worker pool、定时任务……只要启了新 goroutine,就得自己包一层防护,不存在“全局中间件”式的 recover。
立即学习“go语言免费学习笔记(深入)”;
- 错误写法:
go doWork(),而doWork里没 defer/recover → 一 panic 整个进程挂 - 正确模式:
go func() { defer func() { if r := recover(); r != nil { log.Printf("panic in worker: %v", r) } }(); doWork() }() - 如果
doWork内部还启了子 goroutine,那些子 goroutine 同样需要自己的 defer/recover - recover 后别碰已损坏状态:比如切片越界 panic 后,那个 slice 很可能已不可用,再读写可能二次 panic
真正安全的退出组合:context + WaitGroup + Goexit 或 recover
单靠 recover() 或 Goexit() 都不够。生产环境常见模式是三者协同:
- 用
context.Context传递取消信号(比如超时、主动 shutdown) - 用
sync.WaitGroup等待所有 worker goroutine 自行退出(注意:Add 必须在 go 前,Done 必须在 goroutine 内) - 每个 worker 内部:监听
ctx.Done()+defer/recover防崩 + 必要时runtime.Goexit()快速退出深调用 - 主 goroutine 的
WaitGroup.Wait()必须用select包裹,才能响应 context 取消,否则卡死
最容易被忽略的是:recover 后的清理逻辑(如 close channel、释放锁)必须放在 defer 里,而不是 recover 块内 —— 因为 recover 成功不代表状态完好,后续业务代码可能再次 panic。


















