Variável de Loop & Bugs de Fechamento de Goroutine
Você inicia dez goroutines em um loop e todas imprimem 10 em vez de 0 a 9.
Busque em todas as páginas da documentação
Você inicia dez goroutines em um loop e todas imprimem 10 em vez de 0 a 9.
Esta página explica a captura de variáveis de loop, o que o Go 1.22 mudou e os padrões defensivos que ainda importam para defer, closures aninhados e bibliotecas que visam versões mais antigas do Go.
Um closure captura variáveis por referência, não por um snapshot de iteração de loop.
Antes do Go 1.22, a única variável de valor i do loop for ou range era reutilizada a cada iteração, então as goroutines iniciadas no loop frequentemente observavam o valor final.
Go 1.22+ cria uma nova variável por iteração para for e range, corrigindo o caso comum, mas defer dentro de loops, closures manuais de variáveis externas e código compilado com diretivas go mais antigas ainda exigem cópias explícitas.
Cartão de receita de referência rápida - pronto para copiar e colar.
// Defensivo (todas as versões): copiar antes da goroutine
for _, item := range items {
item := item
go func() {
process(item)
}()
}
// Go 1.22+ vars por iteração (padrão com go 1.22 em go.mod)
for i := range n {
go func() {
process(i) // cada goroutine recebe seu próprio i
}()
}
// Passar como parâmetro (mais claro)
for _, item := range items {
go func(it Item) {
process(it)
}(item)
}Quando usar isso:
errgroup ou pools de workers sobre uma coleçãot.Parallel() dentro de um rangepackage main
import (
"fmt"
"sync"
)
func buggy(ids []int) {
var wg sync.WaitGroup
for _, id := range ids {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("buggy", id) // pode imprimir apenas o último id com a semântica pré-1.22
}()
}
wg.Wait()
}
func fixed(ids []int) {
var wg sync.WaitGroup
for _, id := range ids {
wg.Add(1)
id := id
go func() {
defer wg.Done()
fmt.Println("fixed", id)
}()
}
wg.Wait()
}
func main() {
ids := []int{1, 2, 3}
buggy(ids)
fixed(ids)
}O que isso demonstra:
id pela referência ao endereço da variável do loopid := id cria uma ligação por iteração segura para a goroutinego func(id int) { ... }(item) documenta a intenção na revisão de códigoWait() ou use errgroup para que o main não saia antes que as goroutines executemfor e range sob a versão de linguagem go 1.22defer em um loop ainda empilha defers até que a função retorne, não por iteração| Situação | Risco | Mitigação |
|---|---|---|
go 1.22 em go.mod, loop padrão | Menor para captura direta | Ainda copiar para APIs públicas |
go 1.21 ou //go:build mais antigo | Alto | Sempre v := v ou parâmetro |
defer dentro do loop | Alto (vazamento de recurso) | Função inline ou coletar defers |
t.Parallel() em subtestes | Médio | Copiar variável de loop antes do corpo t.Run |
// defer em loop: RUIM - todos os defers rodam na saída da função
for _, f := range files {
defer f.Close()
}
// BOM - função com escopo por iteração
for _, f := range files {
func() {
defer f.Close()
// trabalhar com f
}()
}defer em loops - Defers se acumulam; arquivos permanecem abertos até o final da função. Correção: função aninhada por iteração.t.Run(tc.name, func(t *testing.T) { t.Parallel(); use(tc) }) precisa de tc := tc. Correção: copiar antes de t.Run.| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
Parâmetro para goroutine go f(x)(item) | Intenção mais clara | Muitos parâmetros poluem a chamada |
Cópia de sombra id := id | Correção rápida em loops | Sombreamento confunde leitores que não conhecem a regra |
| Enviar em canal por item | Designs de pipeline | Fan-out simples não precisa de canal |
| Processamento sequencial | N pequeno ou ordenação necessária | Precisa de paralelismo para vazão |
Corrigiu as variáveis for e range por iteração quando o módulo usa a versão de linguagem Go 1.22.
defer em loops, capturas de escopo externo e diretivas go explícitas ainda exigem cuidado.
Elas compartilham o endereço de uma única variável.
O loop atualiza essa variável antes que as goroutines executem.
Ambos funcionam.
Parâmetros tornam a captura explícita na linha da instrução go para revisores.
Sim - g.Go(func() error { return work(item) }) precisa de item por iteração com semântica segura.
govet copyloopvar relata variáveis de loop referenciadas por closures sem cópia.
Ative no golangci-lint para alvos de linguagem mais antigos.
Sim - for i := 0; i < n; i++ teve a mesma reutilização de variável única pré-1.22.
Go 1.22 cria i por iteração quando a versão da linguagem permite.
Busca mecânica: go func dentro de for com variáveis livres.
Adicione v := v ou passe parâmetro; execute testes com o detector de corrida habilitado.
Execute um loop apertado gerando goroutines que imprimem IDs.
Use -count=100 para estresse; captura incorreta mostra saída uniforme.
Cada recebimento obtém um novo valor.
O bug clássico é range sobre slice/map com goroutines dentro do corpo.
O uso do WaitGroup está bem.
O bug é qual valor de variável a closure da goroutine observa.
Esse experimento precedeu a mudança de linguagem 1.22.
Use go 1.22 em go.mod em vez de experimentos em módulos de produção.
Não - a mesma semântica de variável de loop se aplica.
for & Semântica de range - formas de rangeVersões da Pilha: Esta página foi escrita para Go 1.26.x (GC padrão Green Tea, go fix modernizers - verifique o patch na compilação), chi (última - verifique na compilação), gin (última - verifique na compilação), echo (última - verifique na compilação), google.golang.org/grpc (última - verifique na compilação), sigs.k8s.io/controller-runtime (última - verifique na compilação), kubebuilder (última - verifique na compilação), tinygo (última - verifique os alvos de placa na compilação), wazero (última - verifique na compilação) e golangci-lint (última - verifique o conjunto de linters na compilação).
Revisado por Chris St. John·Última atualização: 19 de jul. de 2026