我定义了一个Cycle类来处理并发任务.我想要的是运行两个函数,每个函数都在Goroutine中,等待它们的完成,并将它们的输出错误组合在一起.但我只有一个错误.每种方法的责任如下:

Run-在Goroutine中运行函数,并收集其错误

WaitAllDone-将所有功能错误合并在一起,并等待所有功能完成

Do1, Do2-测试功能

import (
    "fmt"
    "go.uber.org/multierr"
    "sync"
    "testing"
)

type Cycle struct {
    errChan chan error
    wg sync.WaitGroup
}

func NewCycle() *Cycle {
    return &Cycle{
        errChan: make(chan error),
        wg:      sync.WaitGroup{},
    }
}

// run fn and collect its error into error channel
func (c *Cycle) Run(fn func() error) {
    c.wg.Add(1)
    go func() {
        defer c.wg.Done()
        if err := fn(); err != nil {
            c.errChan <- err
        }
    }()
}

// wait all fn finish and combine their error together
func (c *Cycle) WaitAllDone() error {
    var err error
    go func() {
        for {
            if tmpErr, ok := <-c.errChan; ok {
                err = multierr.Append(err, tmpErr)
            } else{
                break
            }
        }
    }()
    c.wg.Wait()
    close(c.errChan)
    return err
}

func Do1() error {
    return fmt.Errorf("ERR1")
}

func Do2() error {
    return fmt.Errorf("ERR2")
}

func Test41(t *testing.T) {
    c := NewCycle()
    c.Run(Do1)
    c.Run(Do2)
    if err := c.WaitAllDone(); err != nil {
        t.Log(err)
    }
}

最后的t.Log(err)输出ERR1ERR2,但我希望它输出ERR1 ERR2.为什么它漏掉了一个错误.

推荐答案

这是因为(*Cycle).WaitAllDone不会等待收集错误的Goroutine完成.如果运行带有-race标志的代码,有时它可能会报告几个数据竞争错误.以下是其中之一:

$ go test -race .
==================
WARNING: DATA RACE
Write at 0x00c0000a0610 by goroutine 10:
  m.(*Cycle).WaitAllDone.func1()
      /home/zeke/src/temp/76370962/main_test.go:40 +0xb6

Previous read at 0x00c0000a0610 by goroutine 7:
  m.(*Cycle).WaitAllDone()
      /home/zeke/src/temp/76370962/main_test.go:48 +0x14e
  m.Test41()
      /home/zeke/src/temp/76370962/main_test.go:63 +0xa4
  testing.tRunner()
      /snap/go/current/src/testing/testing.go:1576 +0x216
  testing.(*T).Run.func1()
      /snap/go/current/src/testing/testing.go:1629 +0x47

此更改将解决此问题:

  func (c *Cycle) WaitAllDone() error {
    var err error
+   done := make(chan int)
    go func() {
        for {
            if tmpErr, ok := <-c.errChan; ok {
                err = multierr.Append(err, tmpErr)
            } else {
                break
            }
        }
+       close(done)
    }()
    c.wg.Wait()
    close(c.errChan)
+   <-done
    return err
  }

使用range子句可以简化for循环:

func (c *Cycle) WaitAllDone() error {
    var err error
    done := make(chan int)
    go func() {
        for tmpErr := range c.errChan {
            err = multierr.Append(err, tmpErr)
        }
        close(done)
    }()
    c.wg.Wait()
    close(c.errChan)
    <-done
    return err
}

Go相关问答推荐

GetSecretValue,get identity:get credentials:无法刷新缓存的凭据

AWS S3 SelectObjectContent在AWS SDK v2 for Go中不返回结果

如何防止程序B存档/删除围棋中程序A当前打开的文件?

Hugo错误:没有为此项目配置现有内容目录

Golang Gorm Fiber / argon2.Config 未定义

go-chi: 接受带有反斜杠的 url 路径参数

Golang:访问any类型泛型上的字段

是否可以使用标准库构建 Go 二进制文件?

使用GOTK3和librsvg在Go中如何加载内联SVG?

Go time.Parse 无效的 ISO 日期

如何忽略打印达到最大深度限制 go colly

无法使用带有 422 的 go-github 创建提交 - 更新不是快进

如何使用 sync.WaitGroup 来执行所有的 goroutine?

如何将整数哈希细分为范围

Go:如何在将 float64 转换为 float32 时判断精度损失

Go:从 ssl 证书中获取 'subject/unstructeredName' 的值

在 Go 中将十六进制转换为带符号的 Int

为什么在 goroutine 中声明时,benbjohnson/clock 模拟计时器不执行?

我该如何做错误处理惯用的方式

Scanner.Buffer - 最大值对自定义拆分没有影响?