Pakiet Context w Go w 2026: Anulowanie, Timeouty i Pytania Rekrutacyjne
Kompleksowy przewodnik po pakiecie context w Go - od interfejsu Context przez WithCancel, WithTimeout, po zaawansowane wzorce i pytania rekrutacyjne.

Pakiet context w Go stanowi standardowy mechanizm przekazywania terminów, sygnałów anulowania i wartości związanych z żądaniem między granicami API. Każda produkcyjna aplikacja Go wykorzystuje context: handlery HTTP, zapytania do bazy danych, wywołania gRPC i procesy w tle zależą od niego w celu eleganckie zamykanie i zarządzanie timeoutami.
Context jest jednym z najczęściej poruszanych tematów na rozmowach kwalifikacyjnych z Go. Rekruterzy oczekują, że kandydaci wyjaśnią propagację contextu, zademonstrują właściwą obsługę anulowania i unikną typowych pułapek, takich jak przechowywanie contextu w strukturach.
Interfejs Context i Jego Cztery Metody
Interfejs Context definiuje cztery metody, które musi spełniać każda implementacja contextu. Zrozumienie tych metod stanowi podstawę efektywnego wykorzystania contextu.
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() zwraca kanał tylko do odczytu. Gdy ten kanał zostaje zamknięty, każda gorutyna nasłuchująca go natychmiast otrzymuje sygnał. Wzorzec <-ctx.Done() blokuje do momentu wystąpienia anulowania. Err() wyjaśnia przyczynę: albo context.Canceled przy jawnym anulowaniu, albo context.DeadlineExceeded gdy upłynął timeout lub termin.
Tworzenie Contextów za Pomocą Background i TODO
Dwie funkcje tworzą contexty główne: context.Background() i context.TODO(). Obie zwracają niepuste contexty, które nigdy nie są anulowane.
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() służy jako rodzic dla przychodzących żądań, funkcji main i kodu inicjalizacyjnego. TODO() działa jako placeholder podczas refaktoringu, gdy właściwe źródło contextu nie zostało jeszcze określone. Narzędzia do analizy statycznej mogą oznaczać użycie TODO() do przeglądu.
Anulowanie za Pomocą WithCancel i WithCancelCause
Ręczne anulowanie pozwala gorutynom nadrzędnym sygnalizować potomnym, że praca powinna zostać zatrzymana. Ten wzorzec pojawia się w pulach workerów, zadaniach w tle i implementacjach 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, wprowadzony w Go 1.20, dostarcza bogatszych informacji o błędach niż zwykły WithCancel. Funkcja context.Cause() pobiera błąd przekazany do funkcji cancel, ułatwiając debugowanie w złożonych systemach.
Gotowy na rozmowy o Go?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Timeouty i Terminy dla Obsługi Żądań
Handlery HTTP, wywołania bazy danych i żądania do zewnętrznych API powinny zawsze mieć timeouty. Pakiet context oferuje WithTimeout i WithDeadline w tym celu. Oba tworzą contexty, które automatycznie anulują się po upływie czasu.
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)
}Wzorzec defer cancel() zapewnia zwolnienie zasobów nawet gdy operacja zakończy się przed timeoutem. Pominięcie tego wywołania powoduje wyciek zasobów: gorutyna timera contextu pozostaje aktywna do momentu wygaśnięcia timeoutu.
Wartości Związane z Żądaniem za Pomocą WithValue
WithValue dołącza dane związane z żądaniem do contextu. Typowe przypadki użycia obejmują identyfikatory żądań, tokeny uwierzytelniające i spany tracingu. Oficjalna dokumentacja podkreśla konieczność używania niestandardowych typów jako kluczy, aby uniknąć kolizji.
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))
}Wartości contextu tworzą niezmienialny łańcuch: każde wywołanie WithValue tworzy nowy context opakowujący rodzica. Wyszukiwania przechodzą przez ten łańcuch, co sprawia, że głęboko zagnieżdżone wartości są wolniejsze w dostępie. Należy przechowywać tylko dane związane z żądaniem, a nie konfigurację aplikacji czy opcjonalne parametry.
Narzędzia WithoutCancel i AfterFunc
Go 1.21 dodał WithoutCancel dla operacji, które muszą się zakończyć niezależnie od anulowania rodzica, takich jak zadania sprzątające. AfterFunc planuje callback, gdy nastąpi anulowanie contextu.
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 zachowuje wartości z contextu rodzica, ale ignoruje jego sygnał anulowania. Ten wzorzec nadaje się do logowania, emisji metryk i commitów do bazy danych, które muszą się powieść niezależnie od wyniku żądania.
Pytania i Odpowiedzi Rekrutacyjne
Przygotowanie do technicznej rozmowy kwalifikacyjnej z Go wymaga zrozumienia wewnętrznego działania contextu i najlepszych praktyk. Te pytania pojawiają się często na rozmowach w firmach wykorzystujących Go do usług backendowych. Więcej materiałów przygotowawczych do rozmów o Go można znaleźć w module pytań rekrutacyjnych o pakiecie Context.
Dlaczego context powinien być pierwszym parametrem?
Zespół Go ustanowił tę konwencję, aby propagacja contextu była widoczna i spójna. Umieszczenie contextu na pierwszym miejscu sygnalizuje czytelnikom, że funkcja respektuje anulowanie. Biblioteka standardowa stosuje ten wzorzec: http.Request.Context(), database/sql.QueryContext() i grpc.UnaryInterceptor wszystkie oczekują contextu jako pierwszego argumentu.
Co się stanie, jeśli przechowasz context w strukturze?
Przechowywanie contextu łamie model cyklu życia żądania. Struktura może przeżyć żądanie, dla którego została utworzona, powodując, że operacje używają przestarzałych sygnałów anulowania lub pomijają nowe terminy. Dokumentacja contextu wyraźnie ostrzega przed tym wzorcem.
// 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)
}Jak obsługiwać context w gorutynach?
Gorutyny muszą sprawdzać ctx.Done() w swojej głównej pętli lub instrukcji select. Ignorowanie kanału done tworzy wycieki gorutyn: funkcja nadrzędna zwraca, ale gorutyna kontynuuje zużywanie zasobów.
// 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)
}
}
}Kiedy należy używać context.TODO()?
Należy używać TODO() podczas przyrostowego refaktoringu przy dodawaniu obsługi contextu do starszego kodu. Oznacza to miejsca wymagające właściwej propagacji contextu. Kod produkcyjny powinien ostatecznie zastąpić wszystkie wywołania TODO() rzeczywistymi contextami z handlerów żądań lub inicjalizacji aplikacji. Więcej o wzorcach współbieżności w Go można znaleźć w artykule Go Concurrency: Goroutines and Channels.
Typowe Błędy, Których Należy Unikać
Nieprawidłowe użycie contextu prowadzi do subtelnych błędów, które ujawniają się pod obciążeniem lub podczas sekwencji zamykania. Rozpoznanie tych wzorców zapobiega incydentom produkcyjnym.
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() {}Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Kluczowe Wnioski dotyczące Używania Go Context w Produkcji
- Context należy przekazywać jako pierwszy parametr do funkcji, nigdy nie przechowywać go w strukturach
- Zawsze należy wywołać
defer cancel()poWithTimeout,WithDeadlinelubWithCancel, aby zapobiec wyciekom zasobów - Używanie
WithCancelCauseicontext.Cause()poprawia debugowanie błędów w złożonych łańcuchach anulowania - Sprawdzanie
<-ctx.Done()w pętlach gorutyn umożliwia eleganckie zamknięcie i zapobiega wyciekom - Niestandardowe typy kluczy dla
WithValuepozwalają uniknąć kolizji między pakietami WithoutCancelpowinien być stosowany dla operacji sprzątających, które muszą się zakończyć niezależnie od anulowania rodzica- Preferowanie
context.Background()dla inicjalizacji aplikacji icontext.TODO()tylko podczas refaktoringu
Znajdziesz błąd w Go?
Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Autor:
Anthony Fillion-MailletZałożyciel SharpSkill
Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.
Zaktualizowano 29 sierpnia 2026
Tagi
Udostępnij
Powiązane artykuły

Obsługa błędów w Go w 2026: wzorce, opakowywanie i pytania rekrutacyjne
Kompleksowy przewodnik po obsłudze błędów w Go: interfejs error, błędy wartownicze, opakowywanie z %w, errors.Is, errors.As oraz pytania pojawiające się na rozmowach kwalifikacyjnych.

Wzorce projektowe w Go: kluczowe wzorce i pytania rekrutacyjne dla programistów Go
Przegląd kluczowych wzorców projektowych w Go: Functional Options, Strategy, Factory, Observer oraz Middleware. Praktyczne przykłady kodu i pytania rekrutacyjne dla programistów Go.

25 najczęstszych pytań rekrutacyjnych Go: kompletny przewodnik dewelopera
Opanuj rozmowy o pracę z Go, znając 25 najczęstszych pytań. Goroutiny, kanały, interfejsy i wzorce współbieżności z przykładami kodu.