errgroup在golang中的使用是否受限制?

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

Is errgroup in golang restrictive in its usage?

问题

我正在学习golang,对于在使用context时与errgroup包混淆不清。这是我的简单代码:

package main

import (
	"context"
	"errors"
	"fmt"
	"time"

	"golang.org/x/sync/errgroup"
)

func main() {
	fmt.Println("..................")
	ctx := context.Background()
	group, ctx := errgroup.WithContext(ctx)
	ctx, cancel := context.WithCancel(ctx)

	group.Go(func() error {
		//return errors.New("Error 1")
		time.Sleep(8 * time.Second)
		fmt.Println("Sleep 1 ended..................")
		cancel()
		return errors.New("Error 1")
	})

	group.Go(func() error {
		//return errors.New("Error 1")
		time.Sleep(2 * time.Second)
		fmt.Println("Sleep 2 ended..................")
		cancel()
		return errors.New("Error 2")
	})

	err := group.Wait()

	if err != nil {
		fmt.Println("Error: ", err)
	}
	fmt.Println("..................")
}

预期输出为:

..................
Sleep 2 ended..................
Sleep 1 ended..................
Error:  Error 2
..................

group.Wait() "阻塞直到Go方法中的所有函数调用都返回,然后从它们中返回第一个非nil的错误(如果有的话)"。
问题:

  1. 如果我想使用errgroup,但希望等待所有Go方法共享的上下文被取消或所有Go方法的函数调用都返回,该怎么办?
  2. 如果我想使用errgroup,但希望等待其中一个Go方法返回错误,哪个方法会取消上下文并不等待所有方法完成?

我有些感觉errgroup包在使用上有些限制。我漏掉了什么吗?

英文:

I am learning golang and am confused about errgroup package when used with context.
Here is my simple code:

  package main
  
  import (
  	"context"
  	"errors"
  	"fmt"
  	"time"
  
  	"golang.org/x/sync/errgroup"
  )
  
  func main() {
  	fmt.Println("..................")
  	ctx := context.Background()
  	group, ctx := errgroup.WithContext(ctx)
  	ctx, cancel := context.WithCancel(ctx)
  
  	group.Go(func() error {
  		//return errors.New("Error 1")
  		time.Sleep(8 * time.Second)
  		fmt.Println("Sleep 1 ended..................")
  		cancel()
  		return errors.New("Error 1")
  	})
  
  	group.Go(func() error {
  		//return errors.New("Error 1")
  		time.Sleep(2 * time.Second)
  		fmt.Println("Sleep 2 ended..................")
  		cancel()
  		return errors.New("Error 2")
  	})
  
  	err := group.Wait()
  
  	if err != nil {
  		fmt.Println("Error: ", err)
  	}
  	fmt.Println("..................")
  
  }

The output, as expected is:

   ..................
   Sleep 2 ended..................
   Sleep 1 ended..................
   Error:  Error 2
   ..................

group.Wait() "blocks until all function calls from the Go method have returned, then returns the first non-nil error (if any) from them."
Questions:

  1. What if I want to use errgroup but want to wait until the
    context shared by all the Go methods is cancelled or all
    function calls from the Go method have returned?
  2. What if I want to use errgroup but want to wait until one of the Go method has returned error, which method will cancel the context and not wait for all to finish?

Somehow I feel that errgroup package is too restrictive in its use. What am I missing?

答案1

得分: 1

似乎仅仅使用errGroup本身无法实现这个目标。

  1. 可能可以在这里使用waitGroup
  2. 可能只在发生错误时调用cancel。或者使用error通道,并等待第一个错误。
英文:

It seems like this cannot be achieved just by using errGroup itself.

  1. Maybe waitGroup can be used here.
  2. Maybe call cancel only if error happened. Or use error channel and wait till first error.

答案2

得分: 1

这个实现回答了你的两个问题:

package main

import (
	"context"
	"errors"
	"fmt"
	"time"

	"golang.org/x/sync/errgroup"
)

func main() {
	ctx := context.Background()

	g, gtx := errgroup.WithContext(ctx)

	g.Go(func() error {
		select {
		case <-time.After(8 * time.Second):
			fmt.Println("Sleep 1 ended..................")
			return errors.New("Error 1")
		case <-gtx.Done():
			return gtx.Err()
		}
	})

	g.Go(func() error {
		select {
		case <-time.After(2 * time.Second):
			fmt.Println("Sleep 2 ended..................")
			return errors.New("Error 2")
		case <-gtx.Done():
			return gtx.Err()
		}
	})

	err := g.Wait()
	if err != nil {
		fmt.Println("Error: ", err)
	}
	fmt.Println("..................")
}

它将打印以下内容:

   ..................
   Sleep 2 ended..................
   Error:  Error 2
   ..................

返回错误会隐式取消上下文。你需要等待 ctx.Done() 显式地放弃执行 goroutine。

使用 time.Sleep 可能被认为是一种反模式,因为它不会响应上下文的取消。在许多情况下,你应该使用:

select {
	case <-time.After(x):
	case <-ctx.Done():
		return ctx.Err()
}
英文:

This implementation answers both of your questions:

package main

import (
	&quot;context&quot;
	&quot;errors&quot;
	&quot;fmt&quot;
	&quot;time&quot;

	&quot;golang.org/x/sync/errgroup&quot;
)

func main() {
	ctx := context.Background()

	g, gtx := errgroup.WithContext(ctx)

	g.Go(func() error {
		select {
		case &lt;-time.After(8 * time.Second):
			fmt.Println(&quot;Sleep 1 ended..................&quot;)
			return errors.New(&quot;Error 1&quot;)
		case &lt;-gtx.Done():
			return gtx.Err()
		}
	})

	g.Go(func() error {
		select {
		case &lt;-time.After(2 * time.Second):
			fmt.Println(&quot;Sleep 2 ended..................&quot;)
			return errors.New(&quot;Error 2&quot;)
		case &lt;-gtx.Done():
			return gtx.Err()
		}
	})

	err := g.Wait()
	if err != nil {
		fmt.Println(&quot;Error: &quot;, err)
	}
	fmt.Println(&quot;..................&quot;)
}

It will print the following:

   ..................
   Sleep 2 ended..................
   Error:  Error 2
   ..................

Returning an error cancels the context implicitly. You need to wait for ctx.Done() to abandon the execution of the go routines explicitly.

Using time.Sleep might be considered as an anti-pattern, as it doesn't acknowledge context cancelation. In many cases you want to use:

select {
case &lt;-time.After(x):
case &lt;-ctx.Done():
return ctx.Err()
}

huangapple
  • 本文由 发表于 2022年2月4日 20:41:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/70986554.html
匿名

发表评论

匿名网友

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

确定