Concurrent use of contexts

April 18, 2025

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.


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


The Context API contract

Today we come to the core of the context package: The Context interface itself. type Context type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key any) any } A Context carries a deadline, a cancellation signal, and other values across API boundaries. Context’s methods may be called by multiple goroutines simultaneously. For clarity, I’ve removed all of the documentation for each of the interface methods in the above quote—we’ll get to those in following emails, and include those there.


Context abuse

We’re nearly through the overview of the context package, when we come across this seemlingly straightforward sentence: Overview … Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions. But is that really so straight forward? Let’s consider some examples. Let’s first tackle what probably is straight-forward: For garden variety optional parameters, context is the wrong tool! Do this: // Connect connects to the service using the provided host and port, or the // default host and/or port if omitted.


Context arguments

Overview … The Context should be the first parameter, typically named ctx: func DoSomething(ctx context.Context, arg Arg) error { // ... use ctx ... } This short sentence includes two “rules”. Let’s talk about both, why they exist, and when to (possibly) violate them. Context should be the first parameter. Why this is a rule is probably a lot less important than following the rule. But let’s consider the why anyway.

Get daily content like this in your inbox!

Subscribe