Go

Is a context safe for concurrent use? What happens if you read ctx.Done() from many goroutines, or call cancel concurrently?

Question 288MediumGo 1.22 to 1.25

Yes. All Context methods and all cancel functions are safe to call from any number of goroutines. The rules:

  • Done() returns the same channel on every call. For cancelCtx it is created lazily on first use (stored in an atomic.Value), so a context whose Done is never called never allocates a channel.
  • Cancellation closes that channel, and a closed channel wakes every receiver, current and future. This broadcast behavior is why Done is "close-only" and never sends a value.
  • Concurrent calls to cancel are serialized by a mutex. The first one sets err and cause; the rest do nothing.
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
for i := range 3 {
	wg.Add(1)
	go func() {
		defer wg.Done()
		<-ctx.Done()
		fmt.Println("worker", i, "stopped:", ctx.Err())
	}()
}
for range 3 {
	go cancel() // racing cancels: safe
}
wg.Wait()
// Prints three "stopped: context canceled" lines, in arbitrary order.

What isn't automatically safe: the values you store. The context only gives you the pointer; any changes to the object behind it need their own synchronization.

More on Context

All 35 Context questions