goroutine panic 无法跨协程传播,必须在每个可能 panic 的 goroutine 内部用 defer/recover 捕获并转为 error 发送;主 goroutine 的 recover 无法捕获子 goroutine 的 panic。

goroutine panic 无法跨协程传播,必须内部 recover
主 goroutine 的 defer/recover 完全捕获不到子 goroutine 的 panic —— 这不是 bug,是 Go 的设计原则:每个 goroutine 有独立栈,panic 不会“飞”出去。常见现象是程序直接崩溃,打印 panic: runtime error: index out of range,哪怕外层写了 recover()。
正确做法是在每个可能 panic 的 goroutine 内部加一层 defer/recover,把 panic 转成 error 再发出去。比如:
go func() {
defer func() {
if r := recover(); r != nil {
var err error
switch v := r.(type) {
case string:
err = fmt.Errorf("panic: %s", v)
case error:
err = fmt.Errorf("panic: %w", v)
default:
err = fmt.Errorf("panic: unknown type %T", v)
}
errCh
- 不写这个
defer/recover,panic 就会终结整个进程 - 别用空
recover()忽略 panic,掩盖问题 - 类型断言要覆盖
string和error,Go 运行时 panic 值可能是任意类型
用带缓冲的 chan error 收集所有错误
多个 goroutine 往同一个 chan error 发送错误时,若 channel 无缓冲且主 goroutine 还没开始接收,发送操作会阻塞,导致 goroutine 泄漏甚至死锁。
缓冲大小至少等于最大可能出错的 goroutine 数量(或设为总任务数),避免阻塞:
errCh := make(chan error, len(tasks)) // tasks 是任务切片
- 缓冲太小(比如
make(chan error, 1))只收得到第一个错误,其余被丢弃或阻塞 - 缓冲太大浪费内存,但比阻塞安全;实际中按任务上限设即可
- 别用
chan 或 <code> 声明,接收方需要双向能力来关闭 - 发送后不必 close channel —— 关闭应由协调者(如
WaitGroup完成后)统一做
配合 sync.WaitGroup 确保所有错误都被发送
不能假定所有 goroutine 都已执行完再读 channel —— 有些可能还没启动,有些已 panic 并发了错误,有些还在跑。需要用 WaitGroup 显式等待全部退出,再关闭 channel,让接收方能安全遍历。
var wg sync.WaitGroup
errCh := make(chan error, len(tasks))
<p>for _, task := range tasks {
wg.Add(1)
go func(t string) {
defer wg.Done()
if err := doSomething(t); err != nil {
errCh </p><p>// 单独 goroutine 等待并关闭
go func() {
wg.Wait()
close(errCh)
}()</p><p>// 主 goroutine 接收所有错误
for err := range errCh {
if err != nil {
log.Printf("task failed: %v", err)
}
}</p>
-
wg.Wait()必须在单独 goroutine 中调用,否则会阻塞主流程 - 关闭 channel 前确保所有 goroutine 已结束(
defer wg.Done()不可少) -
for range会自动退出,无需额外判断 channel 是否关闭 - 如果只关心是否出错,可用
if err, ok := 拿第一个
用 errgroup.Group 替代手写 WaitGroup + channel
手写容易漏关 channel、缓冲设错、recover 写错位置。errgroup 封装了 WaitGroup、错误聚合和 context 取消,更可靠:
import "golang.org/x/sync/errgroup"
<p>g := new(errgroup.Group)
for _, url := range urls {
url := url // 避免闭包变量复用
g.Go(func() error {
resp, err := http.Get(url)
if err != nil {
return err // 自动被捕获并停止后续 goroutine(默认行为)
}
defer resp.Body.Close()
return nil
})
}
if err := g.Wait(); err != nil {
log.Printf("first error: %v", err) // 只返回第一个非 nil 错误
}</p>
-
errgroup默认只返回首个错误,适合“任一失败即终止”场景 - 要用
errgroup.WithContext(ctx)加超时控制,否则卡住不返回 - 它不收集全部错误 —— 如果业务需要知道哪些 URL 失败了,得回退到自定义 channel 方案
- 依赖需显式
go get golang.org/x/sync/errgroup
真正难的不是写对一个 goroutine 的错误处理,而是保证所有 goroutine 都按同一套规则走 —— recover 写在哪、channel 缓冲设多大、谁负责关闭、context 怎么传。漏掉任意一环,错误就悄无声息地消失。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











