go的并发任务如何优雅的实现错误终止

errgroup使用案例

在Go语言中,并发任务通常通过goroutine来实现,而错误处理和任务终止的优雅性则依赖于适当的同步机制和错误传播策略。

场景:

  • 管理一个任务的一组子任务,每个子任务一个协程
  • 每个子任务必须保证都成功,一个出现失败应当立马停止所有子任务
  • 想知道子任务失败的原因

具体案例:

场景:计算一个目录下所有文件的 MD5 值,任何一个文件都需要正确计算,一旦一个任务出现错误立即返回。

分析:多个任务可以并发执行,可以使用WaitGroup管理,但是需要返回错误,可以使用errgroup。

示例代码:

go 复制代码
package main

import (
	"context"
	"crypto/md5"
	"fmt"
	"log"
	"os"
	"path/filepath"

	"golang.org/x/sync/errgroup"
)

func main() {
	m, err := MD5All(context.Background(), ".")
	if err != nil {
		log.Fatal(err)
	}

	for k, sum := range m {
		fmt.Printf("%s:\t%x\n", k, sum)
	}
}

type result struct {
	path string
	sum  [md5.Size]byte
}

// MD5All reads all the files in the file tree rooted at root and returns a map
// from file path to the MD5 sum of the file's contents. If the directory walk
// fails or any read operation fails, MD5All returns an error.
func MD5All(ctx context.Context, root string) (map[string][md5.Size]byte, error) {
	// ctx is canceled when g.Wait() returns. When this version of MD5All returns
	// - even in case of error! - we know that all of the goroutines have finished
	// and the memory they were using can be garbage-collected.
	g, ctx := errgroup.WithContext(ctx)
	paths := make(chan string)

	g.Go(func() error {
		defer close(paths)
		// Walk函数第二个参数是一个回调函数
		return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if !info.Mode().IsRegular() {
				return nil
			}
			select {
			case paths <- path:
			case <-ctx.Done():
				return ctx.Err()
			}
			return nil
		})
	})

	// Start a fixed number of goroutines to read and digest files.
	c := make(chan result)
	const numDigesters = 20
	for i := 0; i < numDigesters; i++ {
		g.Go(func() error {
			for path := range paths {
				data, err := os.ReadFile(path)
				if err != nil {
					return err
				}
				select {
				case c <- result{path, md5.Sum(data)}:
				case <-ctx.Done():
					return ctx.Err()
				}
			}
			return nil
		})
	}
	// g所有任务执行完毕关闭c
	go func() {
		g.Wait()
		close(c)
	}()

	m := make(map[string][md5.Size]byte)
	for r := range c {
		m[r.path] = r.sum
	}
	// Check whether any of the goroutines failed. Since g is accumulating the
	// errors, we don't need to send them (or check for them) in the individual
	// results sent on the channel.
	if err := g.Wait(); err != nil {
		return nil, err
	}
	return m, nil
}

总结:

实现这个功能使用到了

  • 使用context

  • 使用通道(Channel)

  • 使用syc包(sync.WaitGroup)

相关推荐
互联网中的一颗神经元6 小时前
06. Small 对象分配:mcache 无锁路径
开发语言·golang
abcefg_h9 小时前
MCP 实战指南:如何在项目中使用 Model Context Protocol 及其通信原理
开发语言·后端·golang·mcp
ylj_dev10 小时前
从 0 构建 AI Workload Platform(四):从单机工作流内核到可靠控制面
docker·postgresql·架构·golang
FfHUCisI11 小时前
Golang database/sql 标准库基础
数据库·sql·golang
北漂燕郊杨哥12 小时前
CAD Mini Viewer —— 一个纯 Go 编写的轻量 DXF 查看器
golang·cad·dxf·cadview
xcLeigh12 小时前
Go入门:基本数据类型全览与选择指南
android·服务器·golang·教程·go热门
圣殿骑士-Khtangc12 小时前
Go分布式锁实现方案从Redis到etcd的完整对比
golang
聪明蛋子哟1 天前
Stagehand v3多语言SDK:Python/Go/Rust/Java下的浏览器自动化统一方案
python·golang·rust
福大大架构师每日一题1 天前
ragflow v0.27.0 发布:Go 后端全面对齐、智能体搜索与知识编译大升级,超全更新解析
开发语言·后端·golang