goroutine池中panic无法被外层recover捕获,必须在任务函数内自行defer+recover;推荐封装safetask统一处理,并通过channel或errgroup将panic转为error返回。

goroutine池里panic无法被外层recover捕获
你给池子塞一个任务,它在子goroutine里panic了,主goroutine的defer recover()完全收不到——Go语言明确不支持跨goroutine panic传播。池子本身(比如ants或自研的worker loop)只负责调度和复用,不自动帮你包defer/recover。这意味着:每个提交进池子的函数,必须自己处理panic,否则就静默退出,任务丢失、资源不释放、监控无感知。
必须在任务函数内部加defer+recover
不是池子该干的事,是调用方的责任。你在往池子里提交函数时,得确保它入口处自带防护:
- 错误写法:
pool.Submit(func() { riskyOp() })——riskyOp()panic后整个goroutine消失 - 正确写法:
pool.Submit(func() { defer func() { if r := recover(); r != nil { log.Printf("task panic: %v", r) } }(); riskyOp() }) - 更推荐封装一层:
safeTask(func() { riskyOp() }),内部已含defer/recover和debug.Stack()
想把panic转成error返回?用channel或errgroup
单纯打日志不够,有些场景需要通知调用方失败(比如批量任务中某一项出错)。这时不能依赖recover后“继续执行”,而要靠通信传递结果:
- 用带缓冲的
chan error:提交任务时附带一个errCh chan,recover后往里发<code>fmt.Errorf("panic: %v", r) - 结合
errgroup.Group:改用g.Go(func() error { defer ...; return riskyOp() }),panic需先转error(recover()后return fmt.Errorf("panic: %v", r)) - 注意缓冲区大小:如果并发100个任务,
errCh至少make(chan error, 100),否则发送会阻塞
recover后别碰可能损坏的状态
panic发生点附近的变量、连接、锁、map等,很可能处于中间态。常见坑:
- recover后还调用
conn.Write()—— conn可能已在panic路径里被关闭或置nil - recover后继续往
resultChan发数据 —— 该channel可能已被上游close(),触发新panic - 用
err.(error)断言recover值 —— runtime抛的panic(如index out of range)不是error接口,会二次panic;稳妥做法是fmt.Sprintf("%v", r)
真正难的不是加recover,而是判断哪些状态还能用、哪些必须丢弃。线上服务里,一个没清理的http.Client连接,比一百个未捕获的panic更容易引发雪崩。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











