Introduction
Context is a feature added to the Go standard library in version 1.7. It’s primarily used for handling goroutines:
- carrying deadlines
- cancellation signals
- passing request-scoped values
What is Context?
For instance, when a request generates multiple goroutines or calls multiple layers of functions, several problems often arise:
- The parent request has already ended, but the underlying goroutines are still running
- Some operation is stuck, and we hope to cancel everything after a timeout
- We want to carry information like
request idthroughout the entire call chain
Go context wraps a channel to pass between goroutines, defining the reason for its existence (condition or deadline achieved), and this reason should be associated with the context.
// Context carries deadlines, cancellation signals, and request-scoped values// Its methods can be safely used by multiple goroutines simultaneously.type Context interface { // Done returns a channel, // which is closed when this Context is canceled or times out. Done() <-chan struct{}
// Err returns the reason this Context was canceled, // after the Done channel has been closed. Err() error
// Deadline returns the time at which this Context is expected to be canceled (if set). // ok indicates whether a deadline exists. Deadline() (deadline time.Time, ok bool)
// Value returns the value associated with the specified key; // if no value exists, it returns nil. Value(key interface{}) interface{}}Example of Done() <-chan struct{}
The Done() channel is closed only at the end. It returns nil before closing.
- Context is canceled
- Timeout occurs
- Parent Context ends
select {case <-ctx.Done(): // Received cancellation or timeout signal return ctx.Err()case result := <-workChan: return result}Example of Err() error
You learn the reason when the Context is ended. It returns nil before closing.
<-ctx.Done()log.Println(ctx.Err())Example of Deadline() (time.Time, bool)
Check whether the Context has a set deadline.
ok == false: no deadlineok == true: when the deadline is
Example of Value(key interface{}) interface{}
Value allows you to store “request-scoped” data in the Context, for example:
// Upstream creates contextctx = context.WithValue(ctx, "request_id", "abc-123")
// Downstream reads contextreqID := ctx.Value("request_id")Methods That Work with Context
Background
The root node of all Contexts, typically used in the main function, initialization, or tests.
Returns a non-nil empty Context that:
- will never be canceled
- has no deadlines
- carries no values
// ctx1 is the root node (Background)ctx1 := context.Background()
// ctx2 is a child of ctx1ctx2 := context.WithValue(ctx1, "key", "value") // still cannot be canceled (just carries a value)
// ctx3 is a child of ctx2ctx3, cancel := context.WithTimeout(ctx2, 5*time.Second) // can be canceled (has a timeout)defer cancel()context.Background() (will never be canceled) │ └─> WithValue(...) (inherits parent, still cannot be canceled, but carries a value) │ └─> WithTimeout(...) (can be canceled! Automatically cancels after 5 seconds) │ └─> WithCancel(...) (can be canceled! Cancels when cancel() is called)TODO
A placeholder when you are unsure which Context to use.
func someFunction() { // Temporarily uncertain which Context to use, use TODO ctx := context.TODO() anotherFunction(ctx)}WithCancel
When you want to actively terminate all related goroutines when a “certain event occurs.”
ctx, cancel := context.WithCancel(context.Background())defer cancel() // very important to avoid resource leaks
go func() { time.Sleep(2 * time.Second) cancel()}()
<-ctx.Done()fmt.Println(ctx.Err()) // context.CanceledWithDeadline
When you want to “complete before a specified time, actively terminating all related goroutines.”
// API must respond before a specified timedeadline := time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC)ctx, cancel := context.WithDeadline(ctx, deadline)defer cancel()
resp, err := client.DoWithContext(ctx, req)WithTimeout
When you want to actively terminate all related goroutines after a “specific deadline.”
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)defer cancel()
select {case <-time.After(5 * time.Second): fmt.Println("finished work")case <-ctx.Done(): fmt.Println(ctx.Err()) // context.DeadlineExceeded}WithValue
Context values should be based on data transmission for request scope and API data.
// Upstream creates contextctx := context.Background()ctx = context.WithValue(ctx, "request_id", "abc-123")
// Downstream reads contextreqID := ctx.Value("request_id")// Create a private type to avoid key collisiontype ctxKeyRequestID struct{}ctx = context.WithValue(ctx, ctxKeyRequestID{}, "abc-123")Summary
Go Context is an official way to manage the lifecycle of Go Routines based on Channels.
Currently, I only use this feature when interacting with the DB, but perhaps more situations will arise in the future. It’s just an official way to manage the lifecycle of Go Routines.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)defer cancel()
var todos []models.Todocursor, err := db.GetCollection().Find(ctx, bson.M{})Further Reading
- Learning Golang Context!! Never Looked At It! - TheVimeagen
- Go Concurrency Patterns: Context - Go
- Go From Zero to Depth — Part 8: Context Isn’t Cancellation — It’s a Protocol