MiddleКодИногдаЕщё не отвечали
Что выведет этот цикл с горутинами на Go 1.22+ и на Go ≤1.21?
Предскажите, что напечатает эта программа, отдельно для Go 1.22+ и Go ≤1.21, и объясните, почему они различаются. Учтите: горутины выполняются конкурентно, поэтому учитывайте порядок печатаемых значений, а не только сами значения.
func main() {
var wg sync.WaitGroup
for _, v := range []int{1, 2, 3} {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Print(v, " ")
}()
}
wg.Wait()
}
Предскажите вывод.
На Go 1.22+ каждая итерация получает свой v, поэтому горутины печатают 1 2 3 в некотором порядке. На Go ≤1.21 они все делили один v, изменяемый циклом, и обычно печатали 3 3 3. Переносимое исправление — v := v внутри цикла или передача v аргументом.
- ✗Считать, что
go func(){...}()копирует переменную цикла в момент вызова — он захватывает переменную, а не её значение - ✗Принимать поведение
3 3 3за data race, а не за детерминированный захват одной общей переменной - ✗Думать, что скоупинг Go 1.22 ещё и упорядочивает горутины в
1 2 3— порядок по-прежнему не определён
- →Как изменение в Go 1.22 скоупит переменную цикла и стоит ли это аллокации на итерацию?
- →Почему передача
vаргументом функции исправляет ошибку на любой версии Go?
Оглавление
Что выведет код?
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for _, v := range []int{1, 2, 3} {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Print(v, " ")
}()
}
wg.Wait()
}
Вывод
Go 1.22+: 1 2 3 (в некотором порядке)
Go ≤1.21: 3 3 3 (обычно)
Почему так
Замыкание go func(){ ... }() захватывает переменную v, а не её значение в момент запуска.
- Go ≤1.21: переменная цикла
vодна на весь цикл. К тому времени, когда планировщик запускает горутины, цикл уже завершился иvдержит последнее значение3, поэтому все три горутины печатают3. - Go 1.22+: язык создаёт новую
vна каждую итерацию, поэтому каждая горутина захватывает собственную копию и печатает1,2,3. Порядок всё равно не определён — горутины планируются независимо.
⚠️ Это самая частая ловушка на собеседованиях по Go. Переносимое исправление, работающее на любой версии:
for _, v := range []int{1, 2, 3} {
v := v // тень: своя переменная на итерацию
go func() { fmt.Print(v, " ") }()
}
// или передать аргументом: go func(v int){ ... }(v)Оглавление