运行有限的goroutines并使用同步WaitGroup和errgroup优雅地退出的实例

283 阅读1分钟

在下面的例子中,我们在一个循环中用一个缓冲通道运行有限的goroutines,以完成许多工作。每个例子都有自己的要求,并使用不同的软件包,如图所示。

  • errgroup 每次最多运行10个goroutine,完成100个作业,一旦goroutine失败,就强制终止程序,或者在完成后正常存在。然而,如果程序运行时间超过1秒,则优雅地终止它,不需要等待goroutine完成。

  • waitgroup 每次最多运行10个goroutine,一直到100个作业结束。然而,如果程序运行时间超过1秒,则优雅地终止它,而不等待goroutines完成。

package main

import (
	"context"
	"fmt"
	"log"
	"time"

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

var (
	done    = make(chan struct{})
	workers = make(chan struct{}, 10)
)

func main() {
	go func() {
		eg, ctx := errgroup.WithContext(context.Background())

		for i := 1; i <= 100; i++ {
			i := i
			workers <- struct{}{}

			eg.Go(func() error {
				select {
				case <-ctx.Done():
					return ctx.Err()
				default:
					if err := do(ctx, i); err != nil {
						return err
					}
					<-workers
					return nil
				}
			})
		}

		if err := eg.Wait(); err != nil {
			log.Fatalln("force exit", err)
		}

		done <- struct{}{}
	}()

	for {
		select {
		case <-time.After(time.Second):
			log.Println("graceful exit at timeout")
			return
		case <-done:
			log.Println("all done")
			return
		}
	}
}

func do(ctx context.Context, i int) error {
	time.Sleep(time.Millisecond * 100)

	fmt.Println("JOB:", i)

	return nil
}

waitgroup

package main

import (
	"fmt"
	"log"
	"sync"
	"time"
)

var (
	done    = make(chan struct{})
	workers = make(chan struct{}, 10)
	waiter  = &sync.WaitGroup{}
)

func main() {
	go func() {
		for i := 1; i <= 100; i++ {
			waiter.Add(1)
			workers <- struct{}{}

			go func(i int) {
				defer waiter.Done()
				do(i)
				<-workers
			}(i)
		}
		waiter.Wait()
		done <- struct{}{}
	}()

	for {
		select {
		case <-time.After(time.Second):
			log.Println("graceful exit at timeout")
			return
		case <-done:
			log.Println("all done")
			return
		}
	}
}

func do(i int) {
	time.Sleep(time.Millisecond * 100)

	fmt.Println("JOB:", i)
}