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.

results matching ""

    No results matching ""