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)

相关推荐
指尖的爷5 小时前
ARM 架构 Ubuntu(RK3588/aarch64)go开发环境安装手册
ubuntu·架构·golang
名字还没想好☜5 小时前
Go 实现指数退避重试:context 取消、抖动 jitter 与什么时候别重试
后端·golang·go
Casbin开源社区7 小时前
OpenAgent 详解:单二进制自托管 AI Agent 平台,30+ 模型接入、RAG 知识库、MCP 工具调用与 Casbin 工具权限
人工智能·golang·开源
五彩小白1 天前
GO语言装饰器语法
golang
金金计较.1 天前
Go语言-4
开发语言·golang
名字还没想好☜2 天前
Go context.AfterFunc 实战(Go 1.21):context 一取消就自动跑清理,告别手写 goroutine 监听 Done
后端·golang·go
web守墓人2 天前
【goed/ui】自定义组件设计思想篇
linux·windows·ui·golang
chenqianghqu3 天前
go语言SDK升级到1.27更新调试
golang
ttwuai3 天前
Go开源后台管理系统推荐:怎么先排除 Fork、镜像和同名项目?
golang
参.商.4 天前
【Day 53】76. 最小覆盖子串
leetcode·golang