You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go语言:goroutine遇错即报并终止所有协程的惯用实现方式

Go协程出错时立即终止并上报错误的实现模式

问题描述

我正在寻找一种能在某个goroutine遇到错误时立即上报错误的基础实现模式。以下是我当前使用的代码逻辑:

func doSomeWork(num int, errChan chan <- error) {
        if num >= 10 {
                errChan <- errors.New("Greater than 10")
        }
        return
}

const numWorkers int = 25

func run() error {
        var wg sync.WaitGroup
        errChan := make(chan error)
        for i := 0; i < numWorkers; i++ {
                wg.Add(1)
                go func(num int) {
                        defer wg.Done()
                        doSomeWork(num, errChan)
                }(i)
        }
        go func() {
                wg.Wait()
                close(errChan)
        }()
        err, ok := <-errChan
        if !ok {
                return nil
        }
        return  err
}

这段代码能正常工作:当numWorkers小于10时返回nil,大于等于10时返回错误。不过这只是示例,我并不关心worker数量。但我觉得这种实现不够地道,而且当某个协程出错时,其他协程仍在运行,等待所有协程的goroutine也在运行,可能存在协程泄漏问题。请问有没有更地道的实现方式,能在一个协程出错时终止所有协程?

解决方案

在Go生态里,用context.Context实现协程取消控制是最地道的方案,结合错误通道就能做到:一旦某个协程报错,立刻通知所有其他协程停止工作,同时返回错误。

改进后的代码

import (
	"context"
	"errors"
	"sync"
)

func doSomeWork(ctx context.Context, num int) error {
	// 先检查上下文是否已取消,避免执行无效工作
	select {
	case <-ctx.Done():
		return ctx.Err()
	default:
	}

	if num >= 10 {
		return errors.New("Greater than 10")
	}

	// 模拟耗时工作,每隔一段时间检查上下文状态
	for i := 0; i < 100; i++ {
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
			// 这里替换为实际的工作逻辑
		}
	}
	return nil
}

const numWorkers int = 25

func run() error {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel() // 确保函数退出时取消上下文,防止资源泄漏

	var wg sync.WaitGroup
	errChan := make(chan error, numWorkers) // 带缓冲的错误通道,避免发送错误时阻塞

	for i := 0; i < numWorkers; i++ {
		wg.Add(1)
		go func(num int) {
			defer wg.Done()
			if err := doSomeWork(ctx, num); err != nil {
				// 尝试发送错误到通道,若通道已满(已有错误上报)则直接忽略
				select {
				case errChan <- err:
				default:
				}
				cancel() // 触发上下文取消,通知所有协程停止工作
			}
		}(i)
	}

	// 等待所有工作协程完成后关闭错误通道
	go func() {
		wg.Wait()
		close(errChan)
	}()

	// 接收第一个上报的错误
	if err, ok := <-errChan; ok {
		return err
	}
	return nil
}

核心优化点

  • 上下文取消机制:通过context.WithCancel创建可取消上下文,一旦某个协程报错,调用cancel()会立即触发所有关联协程的取消信号,协程通过监听ctx.Done()及时停止工作。
  • 带缓冲错误通道:避免发送错误时阻塞,同时保证第一个错误能被优先捕获,后续错误直接丢弃,符合“立即上报”的需求。
  • 关键节点检查取消信号:在doSomeWork的起始位置和耗时循环中检查上下文状态,确保协程能快速响应取消,避免无意义的资源消耗。
  • defer cancel():无论函数正常返回还是出错,都会最终取消上下文,彻底避免协程泄漏。

这种实现完全贴合Go的设计理念,既解决了原代码的协程泄漏问题,又高效实现了“一出错就终止所有协程并上报”的需求。

内容的提问来源于stack exchange,提问作者pandolf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 02:53:20