Why golang g.cancel() is required in func (g *Group) Wait() error

huangapple go评论87阅读模式
英文:

Why golang g.cancel() is required in func (g *Group) Wait() error

问题

golang的错误组(error group)非常有用,下面是其实现代码:

func (g *Group) Go(f func() error) {
  g.wg.Add(1)
  go func() {
    defer g.wg.Done()
    if err := f(); err != nil {
      g.errOnce.Do(func() {
        g.err = err
        if g.cancel != nil {
          g.cancel()
        }
      })
    }
  }()
}

我理解一旦一个goroutine出现错误,错误组将取消所有其他goroutine。然而,我对下面的Wait函数感到困惑:

func (g *Group) Wait() error {
  g.wg.Wait()
  if g.cancel != nil {
    g.cancel()
  }
  return g.err
}

为什么我们需要在这个函数中再次取消?你能给我一个需要这个取消的案例吗?

我的意思是,在成功的情况下,所有的goroutine都完成了任务,所以不需要再次取消;另一方面,如果一个goroutine失败了,也不需要取消,因为我们在第一个代码块中已经取消了。

英文:

golang's error group is very useful, below is the implementation

func (g *Group) Go(f func() error) {
  g.wg.Add(1)
  go func() {
    defer g.wg.Done()
    if err := f(); err != nil {
      g.errOnce.Do(func() {
        g.err = err
        if g.cancel != nil {
          g.cancel()
        }
      })
    }
  }()
}

I understand that once one go-routine has error, the error group will cancel all other go-routines. However what I am confused is the Wait function below:

func (g *Group) Wait() error {
  g.wg.Wait()
  if g.cancel != nil {
    g.cancel()
  }
  return g.err
}

Why we need to cancel again in this function? Could you provide me a case that this cancel is necessary?

I mean in success case, all go-routines finished task so no need cancel again, on the other hand, if one go-routine failed, no need as well because we canceled in the first code block.

答案1

得分: 4

errgroup的快速回顾:

errgroup.Group将错误传播和上下文取消统一起来。

调用Wait()等待子任务完成;如果任何子任务返回错误,Wait()将该错误返回给调用者。
如果任何子任务返回错误,组的上下文将被取消,从而提前终止所有子任务。

关于组的上下文:
如果父上下文(比如,一个http请求上下文)被取消,组的上下文也会被取消。这有助于避免不必要的工作。例如,如果用户离开页面并取消了http请求,我们可以立即停止。

从代码中可以看出,创建errgroup时会创建一个新的上下文:

// WithContext返回一个新的Group和从ctx派生的关联上下文。
//
// 第一次传递给Go的函数返回非nil错误或第一次Wait返回时,派生的上下文将被取消。
func WithContext(ctx context.Context) (*Group, context.Context) {
	ctx, cancel := context.WithCancel(ctx)
	return &Group{cancel: cancel}, ctx
}

你在组上看到的cancel函数是withCancel函数的返回值,属于这个新的上下文,而不是直接属于组。

这个上下文需要在一个函数返回错误时立即取消,但也需要在操作完成时取消,正如代码文档中所述:“或者第一次Wait返回”。

为什么?
Wait是对整个等待组的等待。

// Wait阻塞,直到Go方法中的所有函数调用都返回,然后从它们中返回第一个非nil错误(如果有)。
func (g *Group) Wait() error {
	g.wg.Wait()
	if g.cancel != nil {
		g.cancel()
	}
	return g.err
}

这意味着整个操作已经完成。它的上下文需要被取消以清理上下文的资源。也许操作甚至启动了一个仍在运行且不再需要的子上下文,需要将取消传播到它。

英文:

Quick review of errgroup:

An errgroup.Group unifies error propagation and context cancelation.

The calling routines Wait() for subtasks to complete; if any subtask returns an error, Wait() returns that error back to the caller.
If any subtask returns an error, the group Context is canceled, which early-terminates all subtasks.

Regarding the group's Context:
If the parent Context (say, an http request context) is canceled, the group Context is also canceled. This helps avoid unnecessary work. For example, if the user navigates away from the page and cancels the http request, we can stop immediately.

You can see from the code that when an errgroup is created, a new context is created:

// WithContext returns a new Group and an associated Context derived from ctx.
//
// The derived Context is canceled the first time a function passed to Go
// returns a non-nil error or the first time Wait returns, whichever occurs
// first.
func WithContext(ctx context.Context) (*Group, context.Context) {
	ctx, cancel := context.WithCancel(ctx)
	return &Group{cancel: cancel}, ctx
}

The cancel function that you see on the group is a return value of the withCancel function, and belongs to this new context, not directly to the group.

This context needs to be cancelled as soon as one function returns an error, but also if the operation completes, as you can see from the code's documentation: "or the first time Wait Returns"

Why?
The wait is a wait on the entire wait group.

// Wait blocks until all function calls from the Go method have returned, then
// returns the first non-nil error (if any) from them.
func (g *Group) Wait() error {
	g.wg.Wait()
	if g.cancel != nil {
		g.cancel()
	}
	return g.err
}

This means that the entire operation is complete. Its context needs to be cancelled to clear up the context's resources. Perhaps the operation even started a child context which is still running and no longer needed, and the cancellation needs to be propagated to it.

huangapple
  • 本文由 发表于 2022年6月29日 14:12:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/72796556.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定