使用通道传递错误是 go 并发 中处理 goroutine 错误的核心方法,通过创建 Error 类型通道让 worker 发送错误,主协程接收并处理。示例中 doWork 函数模拟出错,worker 通过 errCh 发送错误,主函数读取并记录。采用缓冲通道可避免发送阻塞,尤其在多个 worker 场景下,主程序可等待所有完成后再判断是否有错。更优方案是使用 errgroup.Group,它能自动传播首个错误并配合 context 取消其他任务。当任意 goroutine 返回非 nil 错误时,g.Wait()立即返回该错误,其余任务应监听 context 的取消信号退出。对于 panic,需在每个 goroutine 中用 defer+recover 捕获,并将 panic 转为 error 通过通道传出,防止程序崩溃。结合 context 可实现超时控制与统一取消,如设置 2 秒超时,任务若未完成则被中断,确保资源不浪费。综上,Go 推荐通过通信而非共享内存管理并发错误,合理组合channel、errgroup 和 context 能构建健壮的错误处理机制,关键在于设计清晰的错误传播路径,避免遗漏或阻塞问题。

Go 语言 中处理并发goroutine 中的错误,关键在于通过通信来共享数据和状态,而不是通过共享内存。由于每个 goroutine 独立运行,直接捕获子 goroutine 中的 panic 或返回错误并不像同步代码那样简单。以下是几种常见且有效的处理方式。
使用通道(Channel)传递错误
最常见的方式是通过一个专门用于传递错误的通道,让 goroutine 在出错时将错误发送出去,主协程接收并做相应处理。
定义一个 error 类型的通道,多个 goroutine 可以向其发送错误,主程序通过 select 或普通接收操作监听。
示例:
func doWork() error { // 模拟任务 return errors.New("something went wrong") } <p>func worker(errCh chan<- error) {err := doWork() if err != nil {errCh <- err return} errCh <- nil }</p><p>func main() { errCh := make(chan error, 1) // 缓冲通道避免阻塞 </p><pre class="brush:php;toolbar:false;"><pre class="brush:php;toolbar:false;">go worker(errCh) err := <-errCh if err != nil {log.Printf("worker failed: %v", err) }
}
立即学习“go 语言免费学习笔记(深入)”;
使用带缓冲的通道可防止 goroutine 因无法发送错误而阻塞。若启动多个 worker,可等待所有完成后再检查是否有任意错误。
使用 errgroup 包简化管理
errgroup.Group 是官方 golang.org/x/sync/errgroup 提供的 工具,能自动传播第一个返回的错误,并取消其他 goroutine(配合 context 使用)。
示例:
import "golang.org/x/sync/errgroup" <p>func main() { var g errgroup.Group</p><pre class="brush:php;toolbar:false;"><pre class="brush:php;toolbar:false;">for i := 0; i < 3; i++ { i := i g.Go(func() error {// 模拟可能出错的任务 if i == 2 { return fmt.Errorf("task %d failed", i) } time.Sleep(time.Second) return nil }) } if err := g.Wait(); err != nil { log.Printf("at least one task failed: %v", err) }
}
立即学习“go 语言免费学习笔记(深入)”;
只要任意一个 goroutine 返回非 nil 错误,g.Wait()会立即返回该错误,其余任务应通过 context 感知取消信号并尽快退出。
处理 panic:使用 recover 捕获异常
如果 goroutine 中发生 panic,会导致整个程序崩溃,除非手动恢复。可以在每个 goroutine 内部使用 defer+recover 拦截 panic,并将其转换为错误通过通道传出。
func safeWorker(errCh chan<- error) {defer func() {if r := recover(); r != nil {errCh <- fmt.Errorf("panic recovered: %v", r) } }() <pre class="brush:php;toolbar:false;"><pre class="brush:php;toolbar:false;">// 可能触发 panic 的操作 panic("unexpected!")
}
立即学习“go 语言免费学习笔记(深入)”;
这种模式适合在长期运行的 worker 中防止程序意外终止,同时将异常情况反馈给主流程。
结合 Context 实现超时与取消
在并发场景中,错误处理常与超时控制结合。使用 context.Context 可以让主程序在出现错误或超时时主动通知所有 worker 退出,避免资源浪费。
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() <pre class="brush:php;toolbar:false;"><code>var g errgroup.Group g.SetLimit(2) // 控制并发数 for i := 0; i < 3; i++ {i := i g.Go(func() error {select { case <-time.After(3 * time.Second): return fmt.Errorf("task %d timed out", i) case <-ctx.Done(): return ctx.Err() } }) } if err := g.Wait(); err != nil { log.Printf("failed or canceled: %v", err) }</code></pre>
这样即使某个任务出错或上下文取消,整个组都能快速退出。
基本上就这些。Go 推荐“不要通过共享内存来通信,而是通过通信来共享内存”。利用通道、errgroup 和 context 组合,能写出清晰可靠的并发错误处理逻辑。关键是提前设计错误传播路径,避免遗漏或阻塞。不复杂但容易忽略细节。


