【问题标题】:Nested errgroup inside bunch of goroutines嵌套在一堆 goroutine 中的 errgroup
【发布时间】:2020-08-17 19:27:07
【问题描述】:

我对 golang 及其并发原则还很陌生。我的用例涉及对一批实体执行多个 http 请求(针对单个实体)。如果某个实体的任何 http 请求失败,我需要停止它的所有并行 http 请求。此外,我必须管理因错误而失败的实体的数量。我正在尝试在实体goroutines中实现errorgroup,这样如果单个实体的任何http请求失败,errorgroup就会终止并将错误返回给它的父goroutine。但我不确定如何保持错误计数。

func main(entity[] string) {
    errorC := make(chan string) // channel to insert failed entity
    var wg sync.WaitGroup

    for _, link := range entity {
        wg.Add(1)
        // Spawn errorgroup here. errorgroup_spawn
    }

    go func() {
        wg.Wait()   
        close(errorC)    
    }()

    for msg := range errorC {
        // here storing error entityIds somewhere.
    }
}

和这样的错误组

func errorgroup_spawn(ctx context.Context, errorC chan string, wg *sync.WaitGroup) { // and other params
    defer (*wg).Done()
    
   goRoutineCollection, ctxx := errgroup.WithContext(ctx)
    results := make(chan *result)
    goRoutineCollection.Go(func() error {
        // http calls for single entity
        // if error occurs, push it in errorC, and return Error.
        return nil
    })

    go func() {
        goRoutineCollection.Wait()
        close(result)
    }()

   return goRoutineCollection.Wait()
}

PS:我也在考虑应用嵌套错误组,但在运行其他错误组时无法考虑维护错误计数 谁能指导我,这是处理此类现实世界场景的正确方法吗?

【问题讨论】:

  • 单个实体的 http 调用是并发的还是顺序的?如果并发,可能会有多个错误,您是对一个错误感兴趣,还是对所有错误感兴趣?
  • @BurakSerdar 它们是并发的,是的,可能存在多个错误。但是 errorgroup 在遇到第一个错误时立即终止。
  • 有多种方法可以做到这一点。一种方法是传递一个包含错误通道的错误和实体名称的错误结构。另一种方法是为 goroutine 运行 https 调用并侦听错误的每个实体使用一个结构,并将错误放入该结构中的一个字段中。
  • @BurakSerdar 你能详细说明一下第一种方式吗?对于第二种方式,您是否暗示嵌套的 goroutines,其中内部 goroutines 将更新外部提供的结构中的错误?
  • 另外,首先@BurakSerdar 有两件事,我在上面的问题中的方法是否合适?其次,如果可能的话,你能说明嵌套错误组的例子吗?

标签: go goroutine


【解决方案1】:

跟踪错误的一种方法是使用状态结构来跟踪哪个错误来自哪里:

type Status struct {
   Entity string
   Err error
}
...

errorC := make(chan Status) 

// Spawn error groups with name of the entity, and when error happens, push Status{Entity:entityName,Err:err} to the chanel

然后您可以从错误通道中读取所有错误并找出失败的原因。

另一种选择是根本不使用错误组。这让事情变得更加明确,但是否更好还有待商榷:

// Keep entity statuses
statuses:=make([]Status,len(entity))
for i, link := range entity {
   statuses[i].Entity=link
   wg.Add(1)
   go func(i index) {
      defer wg.Done()
      ctx, cancel:=context.WithCancel(context.Background())
      defer cancel()

      // Error collector
      status:=make(chan error)
      defer close(status)
      go func() {
         for st:=range status {
             if st!=nil {
                cancel()  // Stop all calls 
                // store first error
                if statuses[i].Err==nil {
                   statuses[i].Err=st
                }
             }
         }
      }()

      innerWg:=sync.WaitGroup{}
      innerWg.Add(1)
      go func() {
         defer innerWg.Done()
         status<- makeHttpCall(ctx)
      }()
      innerWg.Add(1)
      go func() {
         defer innerWg.Done()
         status<- makeHttpCall(ctx)
      }()
      ...
      innerWg.Wait()

   }(i)
}

一切完成后,statuses 将包含所有实体和相应的状态。

【讨论】:

  • 谢谢。在第二种方法中,所有显式取消、错误处理都可以由 errorgroup 完成(因为我的用例不需要哪种类型的错误)。
  • 是否使用errorgroup是你的选择。我的观点是,不使用 errorgroup 会使事情更明确,而不会增加太多复杂性。
猜你喜欢
  • 2022-09-26
  • 1970-01-01
  • 2017-12-15
  • 2018-01-26
  • 1970-01-01
  • 2020-10-31
  • 2020-11-07
  • 2015-04-07
  • 2017-07-24
相关资源
最近更新 更多