In this example we are running 4 goroutines and waiting for them to finish their jobs. However, if any of them takes longer than 80 milliseconds, we are going to return an error. If such case arises, all the other goroutines will be cancelled without waiting for them to finish their jobs. For this, we are going to use context.WithCancel feature.


Example


package main

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

var wg sync.WaitGroup

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

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

ctx, cancel := context.WithCancel(context.Background())
// Cancel even if everything goes fine without an error to release resources.
defer cancel()

for i := 1; i <= 4; i++ {
wg.Add(1)
go func(i int) {
fmt.Println(i, ": STARTED")
err := doSomething(ctx, &wg, i)
if err != nil {
fmt.Println(err)
cancel()
} else {
fmt.Println(i, ": FINISHED")
}
}(i)
}
wg.Wait()

fmt.Println(">>>>> END")
}

func doSomething(ctx context.Context, wg *sync.WaitGroup, id int) error {
defer wg.Done()

// Pick a random number to simulate time it takes to finish the job.
delay := rand.Intn(100)

select {
case <-ctx.Done():
return fmt.Errorf("%d: CANCELLED (%dms)", id, delay)
default:
// Prevent from blocking.
}

if delay > 80 {
return fmt.Errorf("%d: FAILED (%dms)", id, delay)
}
time.Sleep(time.Duration(delay) * time.Millisecond)

return nil
}

Test


All managed to finish their job no longer than 80 milliseconds.


>>>>> BEGIN
1 : STARTED
2 : STARTED
3 : STARTED
4 : STARTED
2 : FINISHED
4 : FINISHED
3 : FINISHED
1 : FINISHED
>>>>> END

Only 1 managed to finish. Although 3 and 4 were meant to take shorted than 80 milliseconds, they got cancelled because 2 failed before with 84 milliseconds.


>>>>> BEGIN
1 : STARTED
2 : STARTED
2: FAILED (84ms)
3 : STARTED
3: CANCELLED (20ms)
4 : STARTED
4: CANCELLED (14ms)
1 : FINISHED
>>>>> END

Since 1 failed with 99 milliseconds right at the beginning, all the others have been cancelled although they were meant to take shorted than 80 milliseconds.


>>>>> BEGIN
1 : STARTED
1: FAILED (99ms)
2 : STARTED
2: CANCELLED (73ms)
3 : STARTED
3: CANCELLED (20ms)
4 : STARTED
4: CANCELLED (16ms)
>>>>> END

Since 1 failed with 87 milliseconds right at the beginning, all the others have been cancelled. Although 3 was set to fail as well with 84 milliseconds, it didn't have a chance. Instead, it got cancelled as well.


>>>>> BEGIN
1 : STARTED
1: FAILED (87ms)
2 : STARTED
2: CANCELLED (64ms)
3 : STARTED
3: CANCELLED (84ms)
4 : STARTED
4: CANCELLED (77ms)
>>>>> END

All managed to finish apart from 3 which failed with 85 milliseconds. Technically there was nothing to be cancelled because 3 started last anyway.


>>>>> BEGIN
1 : STARTED
2 : STARTED
4 : STARTED
3 : STARTED
3: FAILED (85ms)
2 : FINISHED
1 : FINISHED
4 : FINISHED
>>>>> END