Go Context Package nel 2026: Cancellation, Timeout e Domande da Colloquio
Guida completa al package context di Go per gestire cancellation, deadline e valori request-scoped. Esempi pratici e domande frequenti nei colloqui tecnici.

Il package context di Go fornisce il meccanismo standard per trasportare deadline, segnali di cancellation e valori specifici delle request attraverso i confini delle API. Ogni applicazione Go in produzione utilizza context: handler HTTP, query al database, chiamate gRPC e worker in background dipendono tutti da esso per il graceful shutdown e la gestione dei timeout.
Context è uno degli argomenti più frequentemente richiesti nei colloqui Go. Gli intervistatori si aspettano che i candidati sappiano spiegare la propagazione del context, dimostrare una corretta gestione della cancellation ed evitare errori comuni come la memorizzazione del context nelle struct.
L'Interfaccia Context e i Suoi Quattro Metodi
L'interfaccia Context definisce quattro metodi che ogni implementazione di context deve soddisfare. La comprensione di questi metodi costituisce la base per un utilizzo efficace del context.
type Context interface {
// Deadline returns the time when work should be canceled
Deadline() (deadline time.Time, ok bool)
// Done returns a channel that closes when the context is canceled
Done() <-chan struct{}
// Err returns the reason why Done was closed
Err() error
// Value returns the value associated with key, or nil
Value(key any) any
}Done() restituisce un channel receive-only. Quando questo channel viene chiuso, ogni goroutine in ascolto riceve immediatamente il segnale. Il pattern <-ctx.Done() blocca fino al verificarsi della cancellation. Err() spiega il motivo: context.Canceled quando cancellato esplicitamente, oppure context.DeadlineExceeded quando un timeout o una deadline sono scaduti.
Creare Context con Background e TODO
Due funzioni creano context root: context.Background() e context.TODO(). Entrambe restituiscono context non-nil e vuoti che non vengono mai cancellati.
package main
import (
"context"
"log"
"net/http"
)
func main() {
// Background: the root context for your application
ctx := context.Background()
// Use it as parent for derived contexts
server := &http.Server{Addr: ":8080"}
// TODO: placeholder when context source is unclear
// Static analysis tools can flag context.TODO() for review
processLegacyData(context.TODO())
}
func processLegacyData(ctx context.Context) {
// Context parameter enables future cancellation support
log.Println("Processing data...")
}Background() serve come parent per le request in arrivo, funzioni main e codice di inizializzazione. TODO() funge da placeholder durante il refactoring quando la sorgente corretta del context non è ancora stata determinata. Gli strumenti di analisi statica possono segnalare l'utilizzo di TODO() per revisione successiva.
Cancellation con WithCancel e WithCancelCause
La cancellation manuale permette alle goroutine parent di segnalare alle goroutine figlie che il lavoro deve terminare. Questo pattern appare nei worker pool, nei task in background e nelle implementazioni di graceful shutdown.
package main
import (
"context"
"errors"
"fmt"
"time"
)
func worker(ctx context.Context, id int, results chan<- int) {
for {
select {
case <-ctx.Done():
// Check why cancellation occurred
if cause := context.Cause(ctx); cause != nil {
fmt.Printf("Worker %d stopped: %v\n", id, cause)
}
return
default:
// Simulate work
time.Sleep(100 * time.Millisecond)
results <- id * 10
}
}
}
func main() {
// WithCancelCause provides error context
ctx, cancel := context.WithCancelCause(context.Background())
results := make(chan int, 10)
// Start 3 workers
for i := 1; i <= 3; i++ {
go worker(ctx, i, results)
}
// Collect some results
for i := 0; i < 5; i++ {
fmt.Println("Result:", <-results)
}
// Cancel with a specific reason
cancel(errors.New("shutdown requested by user"))
// context.Cause retrieves the cancellation reason
time.Sleep(50 * time.Millisecond)
fmt.Println("Cause:", context.Cause(ctx))
}WithCancelCause, introdotto in Go 1.20, fornisce informazioni di errore più ricche rispetto al semplice WithCancel. La funzione context.Cause() recupera l'errore passato alla funzione cancel, facilitando il debugging in sistemi complessi.
Pronto a superare i tuoi colloqui su Go?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Timeout e Deadline per la Gestione delle Request
Gli handler HTTP, le chiamate al database e le request a API esterne dovrebbero sempre avere timeout. Il package context offre WithTimeout e WithDeadline per questo scopo. Entrambi creano context che si cancellano automaticamente quando il tempo scade.
package main
import (
"context"
"fmt"
"time"
)
// fetchFromAPI simulates an HTTP call
func fetchFromAPI(ctx context.Context, endpoint string) (string, error) {
// Simulate variable response time
responseTime := time.Duration(100+endpoint[0]%150) * time.Millisecond
select {
case <-time.After(responseTime):
return fmt.Sprintf("Response from %s", endpoint), nil
case <-ctx.Done():
return "", ctx.Err()
}
}
func main() {
// WithTimeout: relative duration
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel() // Always call cancel to release resources
result, err := fetchFromAPI(ctx, "api.example.com/users")
if err != nil {
if err == context.DeadlineExceeded {
fmt.Println("Request timed out")
} else {
fmt.Println("Request canceled")
}
return
}
fmt.Println(result)
// WithDeadline: absolute time
deadline := time.Now().Add(500 * time.Millisecond)
ctx2, cancel2 := context.WithDeadline(context.Background(), deadline)
defer cancel2()
result2, _ := fetchFromAPI(ctx2, "api.example.com/orders")
fmt.Println(result2)
}Il pattern defer cancel() garantisce che le risorse vengano rilasciate anche quando l'operazione si completa prima del timeout. Saltare questa chiamata causa un memory leak: la goroutine del timer del context rimane attiva fino alla scadenza del timeout.
Valori Request-Scoped con WithValue
WithValue associa dati specifici della request a un context. I casi d'uso comuni includono request ID, token di autenticazione e span di tracing. La documentazione ufficiale enfatizza l'uso di tipi personalizzati come chiavi per evitare collisioni.
package main
import (
"context"
"fmt"
"log"
"net/http"
)
// Define custom key types to avoid collisions
type contextKey string
const (
requestIDKey contextKey = "requestID"
userIDKey contextKey = "userID"
)
// middleware adds request ID to context
func requestIDMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := r.Header.Get("X-Request-ID")
if requestID == "" {
requestID = generateRequestID()
}
// Create new context with request ID
ctx := context.WithValue(r.Context(), requestIDKey, requestID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// handler retrieves values from context
func handler(w http.ResponseWriter, r *http.Request) {
requestID, ok := r.Context().Value(requestIDKey).(string)
if !ok {
requestID = "unknown"
}
log.Printf("[%s] Processing request", requestID)
fmt.Fprintf(w, "Request ID: %s", requestID)
}
func generateRequestID() string {
return fmt.Sprintf("req-%d", time.Now().UnixNano())
}
import "time"
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", handler)
http.ListenAndServe(":8080", requestIDMiddleware(mux))
}I valori del context formano una catena immutabile: ogni chiamata a WithValue crea un nuovo context che avvolge il parent. Le ricerche attraversano questa catena, rendendo i valori profondamente annidati più lenti da accedere. Vanno memorizzati solo dati specifici della request, non configurazioni dell'applicazione o parametri opzionali.
Utility WithoutCancel e AfterFunc
Go 1.21 ha aggiunto WithoutCancel per operazioni che devono completarsi indipendentemente dalla cancellation del parent, come i task di cleanup. AfterFunc pianifica una callback quando si verifica la cancellation del context.
package main
import (
"context"
"fmt"
"log"
"time"
)
func saveAuditLog(ctx context.Context, message string) error {
// Use WithoutCancel: audit logs must complete even if request canceled
cleanCtx := context.WithoutCancel(ctx)
// Simulate database write
select {
case <-time.After(50 * time.Millisecond):
log.Printf("Audit: %s", message)
return nil
case <-cleanCtx.Done():
// This branch never executes: WithoutCancel context is never canceled
return cleanCtx.Err()
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
// AfterFunc: register cleanup when context ends
stop := context.AfterFunc(ctx, func() {
fmt.Println("Context ended, running cleanup")
})
// Perform operation
time.Sleep(50 * time.Millisecond)
// Cancel stops the AfterFunc from running if called before context ends
if stop() {
fmt.Println("Cleanup prevented")
}
cancel()
// Audit log completes despite parent cancellation
saveAuditLog(ctx, "Operation completed")
}WithoutCancel preserva i valori del context parent ma ignora il suo segnale di cancellation. Questo pattern è adatto per logging, emissione di metriche e commit di database che devono avere successo indipendentemente dall'esito della request.
Domande e Risposte per Colloqui
La preparazione per un colloquio tecnico Go richiede la comprensione degli aspetti interni del context e delle best practice. Queste domande appaiono frequentemente nei colloqui presso aziende che utilizzano Go per servizi backend. Per ulteriore preparazione ai colloqui Go, consultare il modulo Domande sul Context Package.
Perché context dovrebbe essere il primo parametro?
Il team Go ha stabilito questa convenzione per rendere la propagazione del context visibile e consistente. Posizionare context come primo parametro segnala ai lettori che la funzione rispetta la cancellation. La libreria standard segue questo pattern: http.Request.Context(), database/sql.QueryContext() e grpc.UnaryInterceptor si aspettano tutti context come primo argomento.
Cosa succede se si memorizza context in una struct?
Memorizzare context rompe il modello del ciclo di vita della request. Una struct potrebbe sopravvivere alla request per cui è stata creata, causando operazioni che utilizzano segnali di cancellation obsoleti o perdono nuove deadline. La documentazione del context mette esplicitamente in guardia contro questo pattern.
// BAD: context stored in struct
type Service struct {
ctx context.Context // Never do this
}
// GOOD: pass context to each method
type Service struct{}
func (s *Service) Process(ctx context.Context, data []byte) error {
// Context flows through the call chain
return s.save(ctx, data)
}Come gestire context nelle goroutine?
Le goroutine devono controllare ctx.Done() nel loro loop principale o nella select statement. Ignorare il channel done crea goroutine leak: la funzione parent ritorna, ma la goroutine continua a consumare risorse.
// Correct pattern for long-running goroutines
func processStream(ctx context.Context, stream <-chan Data) {
for {
select {
case <-ctx.Done():
log.Println("Shutting down processor")
return
case data, ok := <-stream:
if !ok {
return
}
handle(data)
}
}
}Quando utilizzare context.TODO()?
TODO() va utilizzato durante il refactoring incrementale quando si aggiunge supporto context a codice legacy. Marca le posizioni che necessitano di una corretta propagazione del context. Il codice in produzione dovrebbe eventualmente sostituire tutte le chiamate TODO() con context reali provenienti da handler delle request o inizializzazione dell'applicazione.
Errori Comuni da Evitare
L'uso improprio del context porta a bug sottili che si manifestano sotto carico o durante sequenze di shutdown. Riconoscere questi pattern previene incidenti in produzione.
package main
import (
"context"
"time"
)
// MISTAKE 1: Ignoring context cancellation
func badWorker(ctx context.Context) {
for {
// Missing select on ctx.Done()
doExpensiveWork() // Never stops when context canceled
}
}
// MISTAKE 2: Not calling cancel
func leakyTimeout() {
ctx, _ := context.WithTimeout(context.Background(), time.Second)
// Timer goroutine leaks until timeout expires
_ = ctx
}
// MISTAKE 3: Using string keys for values
func collisionProne(ctx context.Context) context.Context {
// Different packages might use same string key
return context.WithValue(ctx, "userID", 123) // Bad
}
// CORRECT versions
func goodWorker(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
default:
doExpensiveWork()
}
}
}
func properTimeout() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() // Always release resources
_ = ctx
}
type userIDKey struct{}
func collisionSafe(ctx context.Context) context.Context {
return context.WithValue(ctx, userIDKey{}, 123) // Good
}
func doExpensiveWork() {}Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Punti Chiave per l'Uso di Go Context in Produzione
- Passare context come primo parametro alle funzioni, mai memorizzarlo nelle struct
- Chiamare sempre
cancel()con defer dopoWithTimeout,WithDeadlineoWithCancelper prevenire memory leak - Utilizzare
WithCancelCauseecontext.Cause()per un debugging migliore in catene di cancellation complesse - Controllare
<-ctx.Done()nei loop delle goroutine per abilitare graceful shutdown e prevenire leak - Usare tipi di chiave personalizzati per
WithValueper evitare collisioni tra package - Applicare
WithoutCancelper operazioni di cleanup che devono completarsi indipendentemente dalla cancellation del parent - Preferire
context.Background()per l'inizializzazione dell'applicazione econtext.TODO()solo durante il refactoring
Sapresti trovare il bug in Go?
Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Scritto da
Anthony Fillion-MailletFondatore di SharpSkill
Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.
Aggiornato il 29 agosto 2026
Tag
Condividi
Articoli correlati

Top 25 domande di colloquio Go: guida completa per sviluppatori
Padroneggia i colloqui Go con le 25 domande più frequenti. Goroutine, channel, interfacce e pattern di concorrenza con esempi di codice.

Colloquio Tecnico Go: Goroutine, Channel e Concorrenza nel 2026
Domande di colloquio Go su goroutine, channel e concorrenza con esempi di codice. Preparazione completa per il colloquio tecnico Golang.

Concorrenza in Go: Goroutine e Canali - Guida Completa
Padroneggia la concorrenza in Go con goroutine e canali. Pattern avanzati, sincronizzazione, istruzioni select e best practice con esempi di codice dettagliati.