Cancel All goroutines if Any One of them is Done
April 4, 2021
In concurrent programming, you often have a group of goroutines that you want to terminate simultaneously if any one of them stops.
For example, consider a scenario where you have two goroutines working as a pair: one reads from a source, and the other writes the read result to an output. When either the reader or the writer is done, you want to terminate both.
A common pattern to achieve this is to share a context among all the goroutines and use a done channel to monitor when any of them finishes.
ctx, cancel := context.WithCancel(context.Background())
doneChan := make(chan struct{}, 2)
go writer(ctx, doneChan)
go reader(ctx, doneChan)
// stop all if any one is done
<-doneChan
cancel()
Here is how it works:
- Create a context ctx with a cancel function.
- Create a done channel doneChan.
- Pass the context and the done channel to all
goroutines. Each goroutine will:
- Stop if ctx is canceled.
- Send a message to the channel when done.
- Start all goroutines.
- Wait on the doneChan.
- If a message is received from the done channel, call the cancel function to stop all goroutines.
A complete example:
package main
import (
"context"
"fmt"
"math/rand"
"time"
)
func runner(ctx context.Context, doneChan chan struct{}) {
count := 1000 + rand.Intn(10000)
for i := 0; i < count; i++ {
select {
case <-ctx.Done():
doneChan <- struct{}{}
return
default:
time.Sleep(1 * time.Millisecond)
}
}
doneChan <- struct{}{}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
total := 1000
doneChan := make(chan struct{})
for i := 0; i < total; i++ {
go runner(ctx, doneChan)
}
for i := 0; i < total; i++ {
<-doneChan
if i == 0 {
cancel()
}
}
fmt.Println("all done")
}
golang
concurrency