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)

相关推荐
不老刘8 小时前
基于LiveKit Go 实现腾讯云实时音视频功能
golang·腾讯云·实时音视频
简佐义的博客1 天前
破解非模式物种GO/KEGG注释难题
开发语言·数据库·后端·oracle·golang
恋喵大鲤鱼1 天前
Golang 运算符
golang·运算符
weixin_437398211 天前
转Go学习笔记(2)进阶
服务器·笔记·后端·学习·架构·golang
ac.char1 天前
Golang读取ZIP压缩包并显示Gin静态html网站
golang·html·gin
Cxzzzzzzzzzz1 天前
.golangci.yml文件配置
golang
程序员爱钓鱼2 天前
Go语言项目工程化 — 常见开发工具与 CI/CD 支持
开发语言·后端·golang·gin
月忆3642 天前
Go语言的web框架--gin
golang
GO兔3 天前
开篇:GORM入门——Go语言的ORM王者
开发语言·后端·golang·go