goroutine内panic不加recover会直接终止整个程序;必须在每个goroutine内部用defer/recover兜底,recover仅对同goroutine生效且须在defer中调用。

goroutine里panic不加recover会直接终止整个程序
Go的goroutine是轻量级线程,但它的panic不会被上层自动捕获——一旦某个goroutine panic且没recover,整个进程就退出。爬虫并发时常见网络超时、JSON解析失败、空指针解引用等,都可能触发panic,必须在每个goroutine入口主动加recover()兜底。
- 别把
recover()写在主函数或外层函数里,它只对当前goroutine生效 -
recover()必须紧跟在defer后面,且只能在defer函数中调用才有效 - 不要依赖
recover()来处理可预知错误(比如http.Get返回err != nil),那是错误处理,不是异常恢复
标准recover写法:defer + 匿名函数 + recover()检查
最稳妥的模式是在每个goroutine启动时立即设置defer recover逻辑。注意recover()返回nil表示没发生panic,非nil才是panic值(通常是error或string)。
go func(url string) {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in goroutine for %s: %v", url, r)
}
}()
// 实际抓取逻辑:http.Get、json.Unmarshal、DOM解析等
resp, err := http.Get(url)
if err != nil {
log.Printf("http error for %s: %v", url, err)
return
}
defer resp.Body.Close()
// ...后续处理
}(url)
- 传参要显式传入(如
url string),避免闭包变量在循环中被覆盖 - log要带上下文(如URL、时间戳),否则panic堆栈丢失关键线索
- recover后别继续执行原逻辑——状态已不可信,应直接return
recover无法捕获的“伪panic”:HTTP 4xx/5xx、io.EOF、json.SyntaxError
这些是正常错误类型,不是panic,recover()完全无效。比如json.Unmarshal遇到非法JSON会返回json.SyntaxError,必须用if err != nil判断;io.ReadFull读不够字节会返回io.ErrUnexpectedEOF,也不是panic。
- 常见误判:把
resp.StatusCode >= 400当成需要recover的异常——其实该走错误分支重试或记录 - 第三方库(如
colly)内部可能封装了recover,但你自己的解析逻辑仍需独立防护 - 如果用
html.Parse解析损坏HTML,它可能panic(如空节点操作),这时recover才真正起作用
recover后怎么避免goroutine泄漏和资源堆积
recover只是阻止崩溃,不代表goroutine能安全收尾。如果panic前已申请资源(如打开文件、持有channel发送权、启动子goroutine),recover后不清理,就会泄漏。
- 所有
defer语句在recover前后都会执行,所以文件关闭、锁释放等基础清理仍有效 - 但channel发送(如
ch )若在panic前卡住,recover后需检查channel是否已满或关闭,避免死锁 - 建议配合context控制生命周期:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second),并在IO操作中传入ctx
真正难处理的是recover后无法判断goroutine是否已部分完成任务——比如写了数据库又panic,事务状态不确定。这种场景得靠幂等设计或外部日志对账,recover本身解决不了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











