Коротко — почему так происходит: - Замыкание внутри `go func(){ fmt.Println(i) }()` захватывает переменную `i` по ссылке, не её текущее значение. К моменту выполнения горутины цикл обычно уже дошёл до конца, поэтому все горутины чаще всего печатают текущее (уже увеличенное) значение `i` (в этом примере — ` 5 \,5\,5`). - Поведение неблокирующее и планировщик нелинейный, поэтому результат некстати детерминирован, но часто вы увидите несколько раз одно и то же значение (обычно ` 5 \,5\,5`). Правильные способы захватить текущее значение `i`: 1) Передать `i` как аргумент в анонимную функцию (рекомендованный способ): ```go var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) go func(v int) { defer wg.Done() fmt.Println(v) }(i) // i копируется в v при запуске горутины } wg.Wait() ``` 2) Создать локальную переменную внутри тела цикла и захватить её: ```go var wg sync.WaitGroup for i := 0; i < 5; i++ { v := i wg.Add(1) go func() { defer wg.Done() fmt.Println(v) }() } wg.Wait() ``` Замечания: - Без синхронизации (например, `sync.WaitGroup`) программа может завершиться до выполнения горутин. - В обоих правильных примерах каждая горутина получает свою копию значения `i` в момент итерации, поэтому вывод будет детерминирован: ` 0 \,0\,0`, ` 1 \,1\,1`, ` 2 \,2\,2`, ` 3 \,3\,3`, ` 4 \,4\,4` (в любом порядке исполнения горутин).
- Замыкание внутри `go func(){ fmt.Println(i) }()` захватывает переменную `i` по ссылке, не её текущее значение. К моменту выполнения горутины цикл обычно уже дошёл до конца, поэтому все горутины чаще всего печатают текущее (уже увеличенное) значение `i` (в этом примере — ` 5 \,5\,5`).
- Поведение неблокирующее и планировщик нелинейный, поэтому результат некстати детерминирован, но часто вы увидите несколько раз одно и то же значение (обычно ` 5 \,5\,5`).
Правильные способы захватить текущее значение `i`:
1) Передать `i` как аргумент в анонимную функцию (рекомендованный способ):
```go
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(v int) {
defer wg.Done()
fmt.Println(v)
}(i) // i копируется в v при запуске горутины
}
wg.Wait()
```
2) Создать локальную переменную внутри тела цикла и захватить её:
```go
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
v := i
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(v)
}()
}
wg.Wait()
```
Замечания:
- Без синхронизации (например, `sync.WaitGroup`) программа может завершиться до выполнения горутин.
- В обоих правильных примерах каждая горутина получает свою копию значения `i` в момент итерации, поэтому вывод будет детерминирован: ` 0 \,0\,0`, ` 1 \,1\,1`, ` 2 \,2\,2`, ` 3 \,3\,3`, ` 4 \,4\,4` (в любом порядке исполнения горутин).