Некоторая путаница может возникнуть при использовании замыканий с конкурентностью. Рассмотрим следующую программу:
func main() {
done := make(chan bool)
values := []string{"a", "b", "c"}
for _, v := range values {
go func() {
fmt.Println(v)
done <- true
}()
}
// ждем завершения всех go-процедур перед выходом
for _ = range values {
<-done
}
}
Можно ошибочно ожидать, что a,b,c
будет отображаться как результат. Вместо этого вы, вероятно, увидите c,c,c
. Это потому, что каждая итерация цикла использует один и тот же экземпляр переменной v
, поэтому каждое замыкание разделяет эту единственную переменную. Когда замыкание выполняется, оно печатает значение v
в момент выполнения fmt.Println
, но v
, возможно, был изменен с момента запуска программы. Чтобы помочь обнаружить эту и другие проблемы, прежде чем они возникнут, запустите go vet
.
Чтобы привязать текущее значение v
к каждому замыканию при его запуске, необходимо изменить внутренний цикл для создания новой переменной на каждой итерации. Одним из способов является передача переменной в качестве аргумента для замыкания:
for _, v := range values {
go func(u string) {
fmt.Println(u)
done <- true
}(v)
}
В этом примере значение v
передается в качестве аргумента анонимной функции. Это значение затем доступно внутри функции как переменная u
.
Еще проще создать новую переменную, используя стиль объявления, который может казаться странным, но отлично работает в Go:
for _, v := range values {
v := v // создаем новую 'v'.
go func() {
fmt.Println(v)
done <- true
}()
}
Это поведение языка, что не происходит определения новой переменной для каждой итерации, возможно, было ошибкой в ретроспективе. Это может быть решено в более поздней версии, но, для совместимости, не может измениться в версии Go 1.
Читайте также:
- Эффективный Go: параллелизм, go-процедуры (goroutines)
- Эффективный Go: каналы
- Эффективный Go: повторная декларация и переназначение
Комментариев нет:
Отправить комментарий