06 — Goroutines, channels, and context
Goal
Understand the concurrency and cancellation tools that gRPC servers use.
Step 1: Run a goroutine and wait for it
Replace the temporary server/sandbox/main.go from the prior chapter with this concurrency example:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for _, name := range []string{"API", "storage", "client"} {
name := name
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("ready:", name)
}()
}
wg.Wait()
}
Run go run ./server/sandbox from the repository root. The lines may appear in different orders. The wait group keeps the main function alive until each goroutine finishes. The local name := name makes the captured loop value explicit and portable across older toolchains.
Step 2: Use a channel
Replace the body with a buffered channel of strings, start one goroutine that sends a message, receive it in main, and close only channels whose sender has finished. Channels coordinate goroutines; a sync.Mutex protects shared state. Choose one clear ownership pattern.
A goroutine is a lightweight unit of concurrent work managed by the Go runtime. Starting one does not mean it has finished; the WaitGroup is a counter that lets the main goroutine wait for completion. A channel passes values between goroutines and can signal that work is available or finished. Use a mutex when multiple goroutines must access the same data structure safely. Concurrency is about organizing overlapping work; it does not guarantee tasks run in a particular order.
Step 3: Cancel work with context
import (
"context"
"fmt"
"time"
)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
select {
case <-time.After(5 * time.Second):
fmt.Println("work finished")
case <-ctx.Done():
fmt.Println("work canceled:", ctx.Err())
}
Place the snippet inside main after imports. This prints a deadline error after roughly two seconds. In application code, pass the context into each operation and stop promptly when ctx.Done() closes.
context.WithTimeout creates a child context that is canceled when the timer expires. Calling cancel releases timer resources early when work completes. Contexts form a parent-child chain, so cancellation of an incoming gRPC request can propagate through the handler, repository, and database call. A context is for request-scoped cancellation and metadata, not a general-purpose place to store application state.
Why this matters for gRPC
Every gRPC handler receives a context. A caller may disconnect or hit its deadline; your handler and database operations should stop instead of continuing useless work. A stream may live a long time, so cancellation and cleanup are essential.
Checkpoint
Run go test -race ./.... Explain the difference between launching a goroutine and waiting for it, and describe when a gRPC request context is canceled.