In examples below we run limited amount of goroutines with a buffered channel in a loop in order to complete many jobs. Each examples have their own requirements and use different packages as listed.



errgroup


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)
}