Errores de Variables de Bucle y Cierre de Goroutines
Lanzas diez goroutines en un bucle y cada una imprime 10 en lugar de 0 a 9.
Busca en todas las páginas de la documentación
Lanzas diez goroutines en un bucle y cada una imprime 10 en lugar de 0 a 9.
Esta página explica la captura de variables de bucle, qué cambió Go 1.22 y los patrones defensivos que aún importan para defer, cierres anidados y bibliotecas dirigidas a versiones anteriores de Go.
Un cierre captura variables por referencia, no por instantánea de iteración del bucle.
Antes de Go 1.22, la única variable de valor i o range del bucle for se reutilizaba en cada iteración, por lo que las goroutines iniciadas en el bucle a menudo observaban el valor final.
Go 1.22+ crea una nueva variable por iteración para for y range, corrigiendo el caso común, pero defer dentro de bucles, cierres manuales de variables externas y código compilado con directivas go más antiguas aún requieren copias explícitas.
Tarjeta de receta de referencia rápida, lista para copiar y pegar.
// Defensivo (todas las versiones): copiar antes de la goroutine
for _, item := range items {
item := item
go func() {
process(item)
}()
}
// Variables por iteración de Go 1.22+ (predeterminado con go 1.22 en go.mod)
for i := range n {
go func() {
process(i) // cada goroutine obtiene su propia i
}()
}
// Pasar como parámetro (más claro)
for _, item := range items {
go func(it Item) {
process(it)
}(item)
}Cuándo usar esto:
errgroup o pools de trabajadores sobre una colecciónt.Parallel() dentro de 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) // puede imprimir solo el último id con la semántica pre-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)
}Lo que esto demuestra:
id por referencia a la dirección de la variable del bucleid := id crea una vinculación por iteración segura para la goroutinego func(id int) { ... }(item) documenta la intención en la revisión del códigoWait() o usa errgroup para que main no salga antes de que se ejecuten las goroutinesfor y range bajo la versión de lenguaje go 1.22defer en un bucle todavía apila los defer hasta que la función retorna, no por iteración| Situación | Riesgo | Mitigación |
|---|---|---|
go 1.22 en go.mod, bucle predeterminado | Menor para captura directa | Aún copiar para APIs públicas |
go 1.21 o //go:build anterior | Alto | Siempre v := v o parámetro |
defer dentro del bucle | Alto (fuga de recursos) | Función en línea o recopilar defer |
t.Parallel() en subpruebas | Medio | Copiar variable de bucle antes del cuerpo de t.Run |
// defer en bucle: MALO - todos los defer se ejecutan al final de la función
for _, f := range files {
defer f.Close()
}
// BUENO - función con ámbito por iteración
for _, f := range files {
func() {
defer f.Close()
// trabajar con f
}()
}defer en bucles - Los defer se acumulan; los archivos permanecen abiertos hasta el final de la función. Corrección: función anidada por iteración.t.Run(tc.name, func(t *testing.T) { t.Parallel(); use(tc) }) necesita tc := tc. Corrección: copiar antes de t.Run.| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
Parámetro a la goroutine go f(x)(item) | Intención más clara | Muchos parámetros complican la llamada |
Copia de sombra id := id | Corrección rápida en bucles | El sombreado confunde a los lectores que no conocen la regla |
| Enviar en canal por elemento | Diseños de pipeline | El simple fan-out no necesita canal |
| Procesamiento secuencial | N pequeño o se requiere ordenación | Necesita paralelismo para el rendimiento |
Corrigió las variables for y range por iteración cuando el módulo utiliza la versión de lenguaje Go 1.22.
defer en bucles, capturas de ámbito externo y directivas go explícitas aún requieren cuidado.
Comparten la dirección de una sola variable.
El bucle actualiza esa variable antes de que se ejecuten las goroutines.
Ambos funcionan.
Los parámetros hacen que la captura sea explícita en la línea de la declaración go para los revisores.
Sí - g.Go(func() error { return work(item) }) necesita item por iteración con semántica segura.
govet copyloopvar informa sobre variables de bucle referenciadas por cierres sin copiarlas.
Habilítalo en golangci-lint para objetivos de lenguaje más antiguos.
Sí - for i := 0; i < n; i++ tenía la misma reutilización de variable única pre-1.22.
Go 1.22 crea i por iteración cuando la versión del lenguaje lo permite.
Ejecuta un bucle ajustado que inicie goroutines que impriman IDs.
Usa -count=100 para estrés; la captura incorrecta muestra salida uniforme.
Cada recepción obtiene un nuevo valor.
El error clásico es range sobre slice/map con goroutines dentro del cuerpo.
El uso de WaitGroup está bien.
El error es qué valor de variable observa el cierre de la goroutine.
Ese experimento precedió al cambio de lenguaje 1.22.
Usa go 1.22 en go.mod en lugar de experimentos en módulos de producción.
No - se aplican las mismas semánticas de variables de bucle.
Búsqueda mecánica: go func dentro de for con variables libres.
Agrega v := v o paso de parámetro; ejecuta pruebas con el detector de carreras habilitado.
for y Semántica de range - formas de rangeVersiones de Stack: Esta página fue escrita para Go 1.26.x (GC predeterminado Green Tea, go fix modernizers - verificar parche en la compilación), chi (última - verificar en la compilación), gin (última - verificar en la compilación), echo (última - verificar en la compilación), google.golang.org/grpc (última - verificar en la compilación), sigs.k8s.io/controller-runtime (última - verificar en la compilación), kubebuilder (última - verificar en la compilación), tinygo (última - verificar objetivos de placa en la compilación), wazero (última - verificar en la compilación) y golangci-lint (última - verificar conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 19 jul 2026