Go: Contexto do Go
Última atualização: 2026-08-26
O contexto é a pedra angular do controle de concorrência em Go — ele permite que os tempos limite, os cancelamentos e os valores sejam transmitidos de forma ordenada pela cadeia de chamadas das goroutines.
Quando seu serviço precisa lidar com dezenas de dependências externas simultaneamente (cada uma com seu próprio tempo limite e lógica de cancelamento), como você pode gerenciá-las de maneira consistente? Nesta lição, você aprenderá a usar o pacote context do Go de forma abrangente.
1. Você aprenderá
context.Background()econtext.TODO()context.WithCancelCancelamento manualcontext.WithTimeouté cancelado automaticamente após um tempo limitecontext.WithDeadlinecancelamento do prazocontext.WithValue: Passagem de valores no nível da solicitação- Contexto: Regras de passe em cadeia
- Prática: Como evitar timeouts em cascata em cadeias de microsserviços
2. A história real de um engenheiro de backend
(1) Problema: Se um componente a montante atingir o tempo limite, todo o sistema entra em falha
Xiaoli é engenheira do sistema 順序. Ela é responsável por uma API REST que depende de três serviços a jusante:
“A API de ordenação chamou três serviços: o serviço de estoque, o serviço de pagamento e o serviço de notificação. Um dia, o serviço de estoque demorou 20 segundos para responder, fazendo com que o uso de memória do meu serviço disparasse, já que todas as goroutines estavam aguardando por ele — e as solicitações de outros usuários também não conseguiam ser processadas. Meu chefe perguntou: ‘Por que a página de ordenação está completamente fora do ar?’”
Análise do problema:
// Bad code: no timeout control
func PlaceOrder(ctx context.Context, order Order) error {
// If InventoryCheck hangs for 30 seconds, the goroutine waits needlessly for 30 seconds
ok, err := InventoryCheck(ctx, order.Items)
if err != nil {
return err
}
// If the payment service times out, 30 seconds have already been wasted; the user has long since given up
err = Charge(ctx, order.Total)
if err != nil {
return err
}
// Notification service also hangs... goroutine leak reaches the limit → OOM
return Notify(ctx, order.UserID)
}
(2) A Solução do Go: Contexto
// context_demo.go
package main
import (
"context"
"fmt"
"time"
)
func main() {
// Root Context
root := context.Background()
// Wrap with WithTimeout: 2-second timeout
ctx, cancel := context.WithTimeout(root, 2*time.Second)
defer cancel() // Ensure resources are released
result := PlaceOrder(ctx, "Ordem-123")
fmt.Println(result)
}
func PlaceOrder(ctx context.Context, orderID string) string {
// Check if Context is already canceled
select {
case <-ctx.Done():
return fmt.Sprintf("Canceled: %v", ctx.Err())
default:
}
// Assign a shorter timeout to each downstream call
checkCtx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel()
ok := InventoryCheck(checkCtx, orderID)
if !ok {
return "Insufficient inventory"
}
return "Order placed successfully"
}
func InventoryCheck(ctx context.Context, orderID string) bool {
// Simulate a slow call
select {
case <-time.After(500 * time.Millisecond):
return true
case <-ctx.Done():
fmt.Printf("InventoryCheck canceled: %v\n", ctx.Err())
return false
}
}
Saída (Normal):
Order placed successfully
(3) Benefícios: com contexto x sem contexto
| Situação | Resultado |
|---|---|
| Sem controle de tempo limite | Vazamento de goroutines, OOM do sistema |
| Tempo manual. Após as verificações | O código está disperso; cada função tem seu próprio tempo limite |
| Controle Unificado de Contexto | Se o Contexto pai for cancelado, todos os Contextos filhos serão cancelados em cascata |
3. Contexto de raiz
(1) Contexto e Tarefas pendentes
package main
import (
"context"
"fmt"
)
func main() {
// Background(): root Context, never canceled
// Used in main functions, initialization, top-level requests
ctx := context.Background()
fmt.Printf("Background: %v\n", ctx)
// TODO(): placeholder when unsure which Context to use
// Marks code that has not yet been integrated with Context and needs refactoring
todo := context.TODO()
fmt.Printf("TODO: %v\n", todo)
}
(2) Contexto vs. Tarefas pendentes
| Contexto | Objetivo | Será cancelado? |
|---|---|---|
Background() |
Nó raiz, ponto de partida para todos os contextos | Nunca |
TODO() |
Espaço reservado indicando que o código ainda não foi integrado ao Context | Nunca |
context.Background() é o nó raiz de todas as árvores de contexto e nunca é cancelado. context.TODO() é usado para marcar código que ainda não foi integrado à cadeia de contexto — você deve substituí-lo por um contexto apropriado o mais rápido possível.
4. context.WithCancel: Cancelamento manual
(1) ▶ Exemplo: Cancelamento manual de uma goroutine
package main
import (
"context"
"fmt"
"time"
)
func Worker(ctx context.Context, id int) {
for {
select {
case <-ctx.Done():
fmt.Printf("Worker %d stopped: %v\n", id, ctx.Err())
return
default:
fmt.Printf("Worker %d working...\n", id)
time.Sleep(500 * time.Millisecond)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go Worker(ctx, 1)
go Worker(ctx, 2)
time.Sleep(2 * time.Second)
fmt.Println("Main goroutine initiating cancellation...")
cancel() // Tell all Workers to stop
// Wait for goroutines to exit
time.Sleep(500 * time.Millisecond)
}
(2) ▶ Exemplo: Cancelamento em cascata
package main
import (
"context"
"fmt"
"time"
)
func handler(ctx context.Context) {
// Child Context inherits parent Context
childCtx, cancel := context.WithCancel(ctx)
defer cancel()
go subTask(childCtx, "task-1")
go subTask(childCtx, "task-2")
// Parent Context canceled → Child Context automatically canceled
select {
case <-time.After(1 * time.Second):
fmt.Println("Handler complete")
case <-ctx.Done():
fmt.Println("Handler canceled")
}
}
func subTask(ctx context.Context, name string) {
select {
case <-time.After(3 * time.Second):
fmt.Printf("%s complete\n", name)
case <-ctx.Done():
fmt.Printf("%s canceled: %v\n", name, ctx.Err())
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go handler(ctx)
time.Sleep(500 * time.Millisecond)
cancel() // Cancel → handler → subTask all cascadingly canceled
time.Sleep(1 * time.Second)
}
sequenceDiagram
participant Main as main()
participant H as handler
participant ST1 as subTask-1
participant ST2 as subTask-2
Main->>H: WithCancel
H->>ST1: WithCancel
H->>ST2: WithCancel
Note over Main,ST2: Normal execution
Main->>Main: cancel()
Main-->>H: ctx.Done()
H-->>ST1: ctx.Done()
H-->>ST2: ctx.Done()
Note over Main,ST2: All cascadingly canceled
cancel(). Mesmo que você use WithTimeout, você deve adiar o cancel(). Caso contrário, os recursos do Context (temporizadores, goroutines) não serão liberados. Regra: Ao criar um WithCancel, WithTimeout ou WithDeadline → execute imediatamente defer cancel().
5. context.WithTimeout: Cancelamento automático ao atingir o tempo limite
(1) ▶ Exemplo: Controle de tempo limite
package main
import (
"context"
"fmt"
"time"
)
func callExternalAPI(ctx context.Context, name string, delay time.Duration) (string, error) {
select {
case <-time.After(delay):
return fmt.Sprintf("%s response", name), nil
case <-ctx.Done():
return "", ctx.Err()
}
}
func main() {
// 1-second timeout
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
// Call two downstream services
result1 := make(chan string, 1)
result2 := make(chan string, 1)
go func() {
r, err := callExternalAPI(ctx, "ServiceA", 500*time.Millisecond)
if err != nil {
result1 <- fmt.Sprintf("ServiceA failed: %v", err)
return
}
result1 <- r
}()
go func() {
r, err := callExternalAPI(ctx, "ServiceB", 1500*time.Millisecond)
if err != nil {
result2 <- fmt.Sprintf("ServiceB failed: %v", err)
return
}
result2 <- r
}()
fmt.Println(<-result1) // ServiceA response (500ms < 1s timeout)
fmt.Println(<-result2) // ServiceB failed: context deadline exceeded (1500ms > 1s)
}
(2) ▶ Exemplo: WithTimeout x WithDeadline
package main
import (
"context"
"fmt"
"time"
)
func main() {
// WithTimeout: timeout 2 seconds from now
timeoutCtx, cancel1 := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel1()
// WithDeadline: specify absolute time
deadline := time.Now().Add(2 * time.Second)
deadlineCtx, cancel2 := context.WithDeadline(context.Background(), deadline)
defer cancel2()
// Both have the same effect
fmt.Printf("timeoutCtx deadline: %v\n", timeoutCtx.Deadline())
fmt.Printf("deadlineCtx deadline: %v\n", deadlineCtx.Deadline())
}
(3) WithTimeout x WithDeadline
| Método | Parâmetros | Finalidade |
|---|---|---|
WithTimeout(parent, 2*time.Second) |
Tempo relativo | Mais comum: “Espere até 2 segundos” |
WithDeadline(parent, time.Time) |
Tempo absoluto | Especifica um prazo: “Concluir até às 15h30” |
6. Passagem de valores no nível da solicitação usando context.WithValue
(1) ▶ Exemplo: WithValue
package main
import (
"context"
"fmt"
)
// Custom key type (avoids conflicts)
type contextKey string
const (
UserIDKey contextKey = "user_id"
TraceIDKey contextKey = "trace_id"
RequestIDKey contextKey = "request_id"
)
func middleware(ctx context.Context) context.Context {
// Get trace ID from request header
ctx = context.WithValue(ctx, TraceIDKey, "trace-123")
ctx = context.WithValue(ctx, RequestIDKey, "req-456")
return ctx
}
func handler(ctx context.Context, userID string) {
ctx = context.WithValue(ctx, UserIDKey, userID)
// Pass to business layer
service(ctx)
}
func service(ctx context.Context) {
// Retrieve values from Context
userID := ctx.Value(UserIDKey).(string)
traceID := ctx.Value(TraceIDKey).(string)
requestID := ctx.Value(RequestIDKey).(string)
fmt.Printf("Processing request: user=%s, trace=%s, request=%s\n",
userID, traceID, requestID)
}
func main() {
ctx := context.Background()
ctx = middleware(ctx)
handler(ctx, "user-007")
}
key em context.WithValue deve ser um tipo personalizado; não é possível usar uma string diretamente. Se dois pacotes usarem a string "user_id" como chave, ocorrerá um conflito. Usar o tipo personalizado type contextKey string é a prática recomendada.
(2) Casos de uso do WithValue
| Cenário | Recomendado | Não recomendado |
|---|---|---|
| TraceID / RequestID | ✅ Passado via WithValue | ❌ Variável global |
| Token de autenticação | ✅ Passado por WithValue | ❌ Parâmetro de função |
| Conexão com o banco de dados | ❌ Obtida por meio de injeção de dependência | ❌ WithValue |
| Parâmetro de negócio | ❌ Parâmetro explícito | ❌ Passagem implícita com valor |
7. Regras de encadeamento de contexto
package main
import (
"context"
"fmt"
"time"
)
type contextKey string
func main() {
root := context.Background()
// Chaining: WithCancel → WithTimeout → WithValue
ctx1, cancel1 := context.WithCancel(root)
defer cancel1()
ctx2, cancel2 := context.WithTimeout(ctx1, 2*time.Second)
defer cancel2()
ctx3 := context.WithValue(ctx2, contextKey("trace"), "trace-007")
// ctx3 inherits ctx1's cancellation + ctx2's timeout + ctx3's value
fmt.Printf("ctx3 deadline: %v\n", ctx3.Deadline())
fmt.Printf("ctx3 value: %v\n", ctx3.Value(contextKey("trace")))
// Cancel ctx1 first → ctx2 and ctx3 both receive the cancellation signal
cancel1()
time.Sleep(10 * time.Millisecond)
fmt.Printf("ctx2 err: %v\n", ctx2.Err())
fmt.Printf("ctx3 err: %v\n", ctx3.Err())
}
(1) Regras para a passagem de contexto
| Regra | Descrição |
|---|---|
| Primeiro parâmetro | O primeiro parâmetro na assinatura de uma função é sempre ctx context.Context |
| Não armazene em uma estrutura | Não armazene Context em um campo de estrutura; em vez disso, passe-o como parâmetro |
| Passagem entre funções | Cada função que precisa estar ciente de cancelamentos ou tempos limite recebe um Contexto |
| Cadeia imutável | Cada chamada ao WithCancel/WithTimeout/WithValue retorna um novo Context |
| Cancelamento em cascata | Ao cancelar um item pai → todos os itens filhos são cancelados; o cancelamento de um item filho não afeta o item pai |
8. Exemplo completo: Controle de tempo limite para rastreamento de microsserviços
// microservice_chain.go
package main
import (
"context"
"fmt"
"math/rand"
"time"
)
// ---------- Simulated downstream services ----------
// Inventory service
type InventoryService struct{}
func (s *InventoryService) Check(ctx context.Context, orderID string) (bool, error) {
// Simulate random delay
delay := time.Duration(rand.Intn(1500)) * time.Millisecond
select {
case <-time.After(delay):
return true, nil
case <-ctx.Done():
return false, ctx.Err()
}
}
// Payment service
type PaymentService struct{}
func (s *PaymentService) Charge(ctx context.Context, amount float64) (string, error) {
delay := time.Duration(rand.Intn(1500)) * time.Millisecond
select {
case <-time.After(delay):
return "pay-" + fmt.Sprintf("%d", time.Now().UnixNano()), nil
case <-ctx.Done():
return "", ctx.Err()
}
}
// Notification service
type NotificationService struct{}
func (s *NotificationService) Send(ctx context.Context, userID, message string) error {
delay := time.Duration(rand.Intn(1500)) * time.Millisecond
select {
case <-time.After(delay):
return nil
case <-ctx.Done():
return ctx.Err()
}
}
// ---------- Business layer ----------
type OrderService struct {
inventory *InventoryService
payment *PaymentService
notify *NotificationService
}
func NewOrderService() *OrderService {
return &OrderService{
inventory: &InventoryService{},
payment: &PaymentService{},
notify: &NotificationService{},
}
}
// PlaceOrder uses Context to control the entire chain's timeout
func (s *OrderService) PlaceOrder(ctx context.Context, userID, orderID string, amount float64) error {
// 1. Inventory check (wait at most 1 second)
invCtx, invCancel := context.WithTimeout(ctx, 1*time.Second)
defer invCancel()
ok, err := s.inventory.Check(invCtx, orderID)
if err != nil {
return fmt.Errorf("inventory check failed: %w", err)
}
if !ok {
return fmt.Errorf("insufficient inventory")
}
// 2. Payment (wait at most 2 seconds)
payCtx, payCancel := context.WithTimeout(ctx, 2*time.Second)
defer payCancel()
paymentID, err := s.payment.Charge(payCtx, amount)
if err != nil {
return fmt.Errorf("payment failed: %w", err)
}
// 3. Notification (wait at most 500ms)
notifyCtx, notifyCancel := context.WithTimeout(ctx, 500*time.Millisecond)
defer notifyCancel()
err = s.notify.Send(notifyCtx, userID, "Order placed: "+orderID)
if err != nil {
// Notification failure does not affect the order (async log recording)
fmt.Printf("Notification failed (logged): %v\n", err)
}
fmt.Printf("Order placed successfully: user=%s, order=%s, payment=%s\n", userID, orderID, paymentID)
return nil
}
// ---------- Client ----------
func main() {
svc := NewOrderService()
// Overall request timeout of 3 seconds
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err := svc.PlaceOrder(ctx, "user-007", "order-123", 99.99)
if err != nil {
fmt.Printf("Order failed: %v\n", err)
}
}
Err() de Context retorna um dos dois valores possíveis: context.Canceled (cancelado manualmente) ou context.DeadlineExceeded (excedido o tempo limite). Dentro de uma cadeia, você pode usar errors.Is(err, context.DeadlineExceeded) para determinar que tipo de cancelamento ocorreu e definir uma estratégia de repetição de acordo com isso.
❓ Perguntas Frequentes
P: Quando se deve usar o Context? R: Em qualquer cenário que exija controle de tempo limite, cancelamento manual ou passagem de valores no nível da solicitação. Isso inclui: processamento de solicitações HTTP, consultas a bancos de dados, chamadas RPC e tarefas agendadas. Não use o Context em funções puramente computacionais que não exijam cancelamento ou controle de tempo limite.
P: Qual é a diferença entre
Background()eTODO()? R: Ambos são contextos raiz que nunca podem ser cancelados, mas têm significados diferentes:Background()é o contexto raiz que você está usando ativamente em seu código;TODO()é um marcador de lugar, indicando que essa seção do código ainda não foi integrada ao Contexto e precisa ser refatorada o mais rápido possível.
P: O que devo escolher,
WithTimeoutouWithDeadline? R: Na maioria dos casos, useWithTimeout(“aguarde até 2 segundos” é mais intuitivo). UseWithDeadlineapenas quando o tempo limite for um tempo absoluto (“concluir até às 15:30:00”).WithTimeoutchama internamenteWithDeadline.
P: O
Contextpode ser cancelado várias vezes? R: O cancelamento só pode ser acionado uma vez — chamadas repetidas aocancel()são seguras (a segunda chamada e as seguintes não produzem efeito). Todas as goroutines que estiverem aguardando oDone()receberão o sinal apenas uma vez.
P: O que acontece quando um cancelamento do contexto pai e um tempo limite ocorrem simultaneamente? R: Os dois são acionados de forma independente, e o que ocorrer primeiro entra em vigor. Se o contexto pai for cancelado manualmente, todos os contextos filhos recebem imediatamente um sinal Done(), mesmo que os tempos limite dos contextos filhos ainda não tenham expirado. Isso garante o caminho mais curto para cancelamentos em cascata.
P: O valor passado para
WithValuepode ser modificado? R: Não. O valor decontext.WithValueé imutável. O que se entende por “modificação” envolve, essencialmente, a criação de um novo Contexto. OWithValuede um Contexto filho não afeta o Contexto pai. Isso garante a segurança de concorrência.
P: Para quais tipos de dados o Context é adequado? R: Ele é adequado apenas para armazenar metadados no nível da solicitação: TraceID, RequestID, UserID e tokens de autenticação. Não é adequado para armazenar parâmetros de negócios (como preço ou quantidade), nem para armazenar conexões ou configurações de banco de dados — esses devem ser passados por meio de injeção de dependência.
📖 Resumo
- Background() / TODO(): Dois tipos de contextos raiz; não podem ser cancelados
- WithCancel: Controlar manualmente o cancelamento da goroutine
- WithTimeout / WithDeadline: cancelamento automático ao atingir o tempo limite
- WithValue: Passagem de valores no nível da solicitação (tipos de chave personalizados)
- Passagem em cadeia: o primeiro argumento é sempre Context
- Cancelamento em cascata: cancelamento do item pai → todos os itens filhos são cancelados
cancel()deve ser chamado usandodefer- Prática: Como evitar timeouts em cascata em cadeias de microsserviços
📝 Exercícios
-
Exercício básico (Dificuldade ⭐): Escreva uma função
FetchWithTimeout(ctx, url string, timeout time.Duration)que utilizecontext.WithTimeoutpara controlar o tempo limite das solicitações HTTP. Quando o tempo limite expirar, a solicitação HTTP subjacente deve ser automaticamente cancelada. -
Problema avançado (Dificuldade ⭐⭐): Implemente um gerenciador de tarefas agendadas que possam ser canceladas simultaneamente. Ele deve suportar: (1) o registro de múltiplas tarefas agendadas (uma por goroutine); (2) o cancelamento individual (chamando a função
cancel); (3) o cancelamento em lote (encadeamento de WithCancel); (4) Todas as tarefas compartilham um único contexto raiz. -
Desafio (Dificuldade ⭐⭐⭐): Simule um sistema de rastreamento distribuído. Requisitos: (1) Passe o TraceID usando
WithValuepor três camadas de chamadas de função (API → Serviço → Banco de Dados); (2) Cada camada possui seu próprio controle de tempo limite (Serviço: 2 s, Banco de Dados: 500 ms); (3) Se ocorrer um tempo limite, a camada atual é cancelada, mas isso não afeta as camadas a montante; (4) Exiba o tempo decorrido e o TraceID para cada etapa. UseDeadline()a partir deContextpara calcular o tempo restante.