Проверяет понимание захвата переменных цикла горутинами в Go и изменения в Go 1.22.
В Go до версии 1.22 переменная, объявленная в цикле for range, создавалась один раз и переиспользовалась на каждой итерации. Это означало, что все горутины, запущенные внутри цикла, захватывали ссылку на одну и ту же переменную, а не её значение на момент запуска. Когда горутина начинала выполняться (возможно, после завершения цикла), переменная уже содержала последнее значение итерации, что приводило к неожиданным результатам.
package main
import (
"fmt"
"time"
)
func main() {
m := map[int]string{1: "a", 2: "b", 3: "c"}
for k, v := range m {
go func() {
fmt.Println(k, v) // захватывает ссылку на k и v
}()
}
time.Sleep(time.Second)
}В этом примере вывод может быть неопределённым: все горутины могут напечатать последнюю пару ключ-значение из map, так как переменные k и v переиспользуются.
Разработчики обходили проблему, создавая локальную копию переменной внутри цикла:
for k, v := range m {
k := k // создаём копию
v := v
go func() {
fmt.Println(k, v)
}()
}Начиная с Go 1.22, переменные цикла создаются заново на каждой итерации, что делает захват безопасным по умолчанию. Теперь приведённый выше код без копирования работает корректно.
Понимание этой проблемы важно для написания корректного конкурентного кода на Go. Хотя Go 1.22 исправил поведение, знание старого механизма помогает читать и поддерживать legacy-код, а также избегать подобных ошибок в других языках с аналогичным поведением.