Overview
…
The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines.
Contexts are built using layers. Every time you call one of the context.With*
functions, the orginal context is wrapped in a new layer. This is the “magic” that makes contexts concurrency safe by design–they’re never mutated once they’re created.
ctx := context.Background() // Create an original context
ctx, cancel = context.WithTimeout(ctx, time.Second) // The new timeout context wraps the original
If, instead, calling context.WithTeimout
added a timeout attribute to the existing context, then it would not be concurrency safe, because another goroutine might be trying to read the original context at the same moment you’re modifying it.
But that’s not a concern! 🎉
Note that the same cannot be said of context values. Whether a value propagated by context is concurrency safe depends on that type. Code such as the following could lead to a data race:
ctx = context.WithValue(ctx, "mykey", myvalue)
/* ... later, in different goroutines ... */
v, _ := ctx.Value("mykey").(MyType)
v.Foo++
/* ... */
v, _ := ctx.Value("mykey").(MyType)
fmt.Println("Foo:", v.Foo)
See https://go.dev/blog/context for example code for a server that uses Contexts.
I’ll leave it as an exercise for the reader to read the linked blog post. You should read it, though.