Beginners' guide to Go Contexts: The Magic Controller of Goroutines
Source: Dev.to
The Basic Check
The most fundamental way to use a context is to check its state manually. This is perfect for long‑running loops or heavy calculations. If a function is a “one‑off” and finishes instantly, a context doesn’t add much value.
func process(ctx context.Context) {
for i := range 1000000 {
// check if the signal says we should stop
if err := ctx.Err(); err != nil {
fmt.Println("stopping early:", err)
return
}
// simulate some work
_ = i
}
}
Without the if err := ctx.Err() check, the goroutine would keep spinning even if the user who started it has already disconnected or timed out.
Powering up with Select
While checking ctx.Err() works for loops, the real magic happens with the select statement. This is how you make a goroutine “listen” for a cancellation signal while it is busy doing something else, like waiting for a channel.
Waiting for a result
Imagine you are fetching data from a slow API. You want the data, but you aren’t willing to wait forever.
func fetch(ctx context.Context) {
resultCh := make(chan string)
go func() {
time.Sleep(5 * time.Second) // simulate a slow task
resultCh **Note:** Using a **buffered channel** (`make(chan string, 1)`) is important. It ensures that if the context times out and we exit the function, the goroutine still running `OldLegacyFunction` can send its result to the channel and exit without getting stuck forever (a goroutine leak).
## The Importance of Cancel and Defer
Whenever you use `context.WithCancel`, `context.WithTimeout`, or `context.WithDeadline`, the standard library gives you back a new `context` and a `cancel` function.
**You must call that cancel function.**
Even if your function finishes successfully, you should call it. The best way to do this is with `defer`.
```go
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
// this ensures that when main finishes, the context is cleaned up
defer cancel()
doWork(ctx)
}
Why is this important?
- Resource Cleanup: Behind the scenes, the parent context keeps track of its children. If you don’t call
cancel, the parent might keep a reference to the child in memory until the parent itself dies, leading to a memory leak. - Stop Ongoing Work: Calling
cancel()sends the signal through thectx.Done()channel. It tells every function using that context: “The party is over, stop whatever you are doing.”