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)

相关推荐
Tony Bai4 小时前
Rust 看了流泪,AI 看了沉默:扒开 Go 泛型最让你抓狂的“残疾”类型推断
开发语言·人工智能·后端·golang·rust
ん贤5 小时前
AI 大模型落地系列|Eino 编排进阶篇:一文讲透编排(Chain 与 Graph)
人工智能·golang·编排·eino
GDAL8 小时前
BoltDB vs SQLite:极简高并发、低配置场景下的终极对比
golang·sqlite·boltdb
ruxingli9 小时前
GoLang的并发如何避免死锁
开发语言·后端·golang
暴躁小师兄数据学院9 小时前
【WEB3.0零基础转行笔记】go编程篇-第12讲:go-zero入门实战
开发语言·笔记·golang·web3·区块链
念何架构之路10 小时前
Go语言表达式的求值顺序
开发语言·后端·golang
低调的JVM15 小时前
Golang下kafka可观测数据采集组件Otelsarama详解
golang·kafka·可观测·opentelemetry
golang学习记1 天前
Go 实时批处理:让数据库少挨点打 [特殊字符]
开发语言·数据库·golang
hutengyi1 天前
go测试问题记录
开发语言·后端·golang
KevinCyao1 天前
Go短信营销接口示例代码:Golang高并发调用营销短信接口的实现方案与代码分享
android·前端·网络·golang·前端框架