[consolog]

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:

  1. Create a context ctx with a cancel function.
  2. Create a done channel doneChan.
  3. 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.
  4. Start all goroutines.
  5. Wait on the doneChan.
  6. 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