In this example we are going to cancel all running goroutines if an error occurs in at least one of them. For that, we will use errgroup from sync package. The errgroup was created for synchronisation, error propagation and Context cancelation for groups of goroutines working as subtasks.


Example


We are going to run 3 goroutines. If any of them would delay more than 80 milliseconds to finish its own job, we will force it to return an error because we are in a hurry. If that happens, all the running goroutines will be terminated too. Otherwise obviously all will work fine.


package main

import (
"context"
"fmt"
"math/rand"
"time"

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

func main() {
fmt.Println(">> START")

// Prevent picking up the same random number all the time for sleeping.
rand.Seed(time.Now().UnixNano())

goErrGroup, ctx := errgroup.WithContext(context.Background())

goErrGroup.Go(func() error {
return doSomething(ctx, 1)
})
goErrGroup.Go(func() error {
return doSomething(ctx, 2)
})
goErrGroup.Go(func() error {
return doSomething(ctx, 3)
})
goErrGroup.Go(func() error {
return doSomething(ctx, 4)
})

// Wait for the first error from any goroutine.
if err:= goErrGroup.Wait(); err != nil {
fmt.Println(err)
}

fmt.Println(">> FINISH")
}

func doSomething(ctx context.Context, id int) error {
fmt.Printf("STR: gorotinne %d\n", id)

// Pick a random number to simulate time it takes to finish the job.
delay := rand.Intn(100)
if delay > 80 {
return fmt.Errorf("FAIL: gorotinne %d: %dms", id, delay)
}
time.Sleep(time.Duration(delay) * time.Millisecond)

fmt.Printf("END: gorotinne %d\n", id)

return nil
}

Tests


In this example, all goroutines managed to finish what they were doing.


>> START
STR: gorotinne 1
STR: gorotinne 2
STR: gorotinne 4
STR: gorotinne 3
END: gorotinne 3
END: gorotinne 1
END: gorotinne 4
END: gorotinne 2
>> FINISH

In this example, only goroutine 1 and 4 managed to finish what they were doing.


>> START
STR: gorotinne 1
STR: gorotinne 2
STR: gorotinne 3
STR: gorotinne 4
END: gorotinne 1
END: gorotinne 4
FAIL: gorotinne 2: 99ms
>> FINISH

This one failed right at the beginning so no goroutines had a chance to finish what they were doing.


>> START
STR: gorotinne 1
STR: gorotinne 2
STR: gorotinne 3
STR: gorotinne 4
FAIL: gorotinne 1: 81ms
>> FINISH