¿Por qué Golang maneja cierres de manera diferente en goroutines?

Considere el siguiente código de Golang (también en el área de juegos Go ):

package main import "fmt" import "time" func main() { for _, s := range []string{"foo", "bar"} { x := s func() { fmt.Printf("s: %s\n", s) fmt.Printf("x: %s\n", x) }() } fmt.Println() for _, s := range []string{"foo", "bar"} { x := s go func() { fmt.Printf("s: %s\n", s) fmt.Printf("x: %s\n", x) }() } time.Sleep(time.Second) } 

Este código produce el siguiente resultado:

 s: foo x: foo s: bar x: bar s: bar x: foo s: bar x: bar 

Asumiendo que esto no es un extraño error del comstackdor, me pregunto por qué a) el valor de s se interpreta de manera diferente en la versión de goroutine y luego en la llamada de func regular yb) y por qué lo asigna a una variable local dentro del bucle. ambos casos.

Los cierres en Go tienen un scope léxico. Esto significa que las variables a las que se hace referencia dentro del cierre desde el scope “externo” no son una copia, sino que de hecho son una referencia. Un bucle for reutiliza la misma variable muchas veces, por lo que está introduciendo una condición de carrera entre la lectura / escritura de la variable s .

Pero x está asignando una nueva variable (con := ) y copiando s , lo que da como resultado que sea el resultado correcto todo el tiempo.

En general, es una buena práctica pasar cualquier argumento que desee para que no tenga referencias. Ejemplo:

 for _, s := range []string{"foo", "bar"} { x := s go func(s string) { fmt.Printf("s: %s\n", s) fmt.Printf("x: %s\n", x) }(s) } 

Consejo: Puede usar el “operador de dirección de obtención” y para confirmar si las variables son iguales o no .

Modifiquemos ligeramente su progtwig para ayudar a nuestra comprensión.

 package main import "fmt" import "time" func main() { for _, s := range []string{"foo", "bar"} { x := s fmt.Println(" &s =", &s, "\t&x =", &x) func() { fmt.Println("-", "&s =", &s, "\t&x =", &x) fmt.Println("s =", s, ", x =", x) }() } fmt.Println("\n\n") for _, s := range []string{"foo", "bar"} { x := s fmt.Println(" &s =", &s, "\t&x =", &x) go func() { fmt.Println("-", "&s =", &s, "\t&x =", &x) fmt.Println("s =", s, ", x =", x) }() } time.Sleep(time.Second) } 

El resultado es:

  &s = 0x1040a120 &x = 0x1040a128 - &s = 0x1040a120 &x = 0x1040a128 s = foo , x = foo &s = 0x1040a120 &x = 0x1040a180 - &s = 0x1040a120 &x = 0x1040a180 s = bar , x = bar &s = 0x1040a1d8 &x = 0x1040a1e0 &s = 0x1040a1d8 &x = 0x1040a1f8 - &s = 0x1040a1d8 &x = 0x1040a1e0 s = bar , x = foo - &s = 0x1040a1d8 &x = 0x1040a1f8 s = bar , x = bar 

Puntos clave:

  • La variable s en cada iteración del ciclo es la misma variable.
  • La variable local x en cada iteración del ciclo son variables diferentes, simplemente tienen el mismo nombre x
  • En el primer bucle for, la parte func () {} () se ejecutó en cada iteración y el bucle solo continúa hasta su próxima iteración después de completar func () {} () .
  • En el segundo bucle for (versión de goroutine), la instrucción go func () {} () se completa instantáneamente. Cuando se ejecutan las instrucciones en el cuerpo de la entidad lo determina el progtwigdor Go. Pero cuando ellos (las declaraciones en el cuerpo del func) comienzan a ejecutarse, ¡el ciclo for ya se completó! Y la variable s es el último elemento en la porción que es bar . Es por eso que tenemos dos “barras” en el segundo para salida de bucle.