preface

In Go language program development, goroutine is used more frequently, so in the daily coding of goroutine error handling, how to do it better?

Generally, our business code is as follows:

func main(a) {
	var wg sync.WaitGroup
	wg.Add(2)
	go func(a) {
		/ /... The business logic
		wg.Done()
	}()
	go func(a) {
		/ /... The business logic
		wg.Done()
	}()
	wg.Wait()
}
Copy the code

In the above code, we run multiple Goroutines, and each coroutine acts independently, so it’s not wise to throw an error message.

Through error logging

The first method is commonly used by writing error logs to a log file, which is then collected and combed with the associated Logtail.

But this introduces a new problem, which is that the methods that call the error log are written all over the place, and the code structure is messy and unintuitive.

Most importantly, there is no specific logic handling or flow for error.

Using channel transmission

You might think of the classic philosophy of Go: Do not communicate by sharing memory; Do not communicate by sharing memory; Revised: Instead, share memory by communicating.

The second method uses a channel to transmit errors in multiple Goroutines:

func main(a) {
	cherrors := make(chan error)
	wgDone := make(chan bool)

	var wg sync.WaitGroup
	wg.Add(2)
	go func(a) {
		/ /... The business logic
		wg.Done()
	}()
	go func(a) {
		/ /... The business logic
		err := returnErr()
		iferr ! =nil {
			cherrors <- err
		}
		wg.Done()
	}()
	go func(a) {
		wg.Wait()
		close(wgDone)
	}()

	select {
	case <-wgDone:
		break
	case err := <-cherrors:
		close(cherrors)
		fmt.Println(err)
	}

	time.Sleep(time.Second)
}

func returnErr(a) error {
	return errors.New("Wrong. I'm an error message.")}Copy the code

While using a channel is much more convenient, writing a channel requires some non-business logic.

Using the sync/errgroup

The third method, is to use the official offer golang.org/x/sync/errgroup standard library:

type Group
    func WithContext(ctx context.Context) (*Group, context.Context)
    func (g *Group) Go(f func(a) error)
    func (g *Group) Wait(a) error
Copy the code
  • Go: Starts a coroutine, calling the given function in a new Goroutine.
  • Wait: Waits for the coroutine to end until all function calls in the Go method return, then returns the first non-zero error, if any.

Combined with its features, it is very easy to handle errors for multiple Goroutines:

func main(a) {
	group := new(errgroup.Group)

	nums := []int{- 1.0.1}
	for _, num := range nums {
		num := num
		group.Go(func(a) error {
			res, err := output(num)
			fmt.Println(res)
			return err
		})
	}

	iferr := group.Wait(); err ! =nil {
		fmt.Println("Get errors: ", err)
	} else {
		fmt.Println("Get all num successfully!")}}func output(num int) (int, error) {
	if num < 0 {
		return 0, errors.New("math: square root error!")}return num, nil
}
Copy the code

Each time a new Goroutine is started, the group. Go method is used directly, and the group. Wait method is used for waiting and error handling.

The advantage of this approach to error handling is that you do not need to focus on non-business logic control code, which is relatively straightforward.

conclusion

In Go, Goroutine is a common method, so we need to know more about goroutine, such as context, error handling, etc

In team development, the code will be easier to read and the number of hidden bugs will be reduced.