TGViewer
Go tests Go tests @golangtests · 7.83K subscribers
Post #764 3.1K
👣 Задача с подвохом, который связан с изменением поведения циклов, введенным в Go 1.22 (и, соответственно, актуальным для Go 1.24).

Задача: Что выведет следующий код при компиляции и запуске с использованием Go 1.24?


package main

import (
"fmt"
"sync"
"time"
)

func main() {
var wg sync.WaitGroup
count := 3

fmt.Println("Запуск горутин...")
wg.Add(count)

for i := 0; i < count; i++ {
// Запускаем горутину в каждой итерации
go func() {
defer wg.Done()
// Имитируем небольшую работу
time.Sleep(time.Duration(10) * time.Millisecond)
// Используем переменную цикла 'i' внутри горутины
fmt.Printf("Горутина видит i = %d\n", i)
}()
}

fmt.Println("Ожидание завершения горутин...")
wg.Wait()
fmt.Println("Все горутины завершены.")
}



Разбор:
Классическая проблема (до Go 1.22): В версиях Go до 1.22 существовала известная проблема с захватом переменных цикла в замыканиях (функциях, создаваемых внутри цикла, как наша анонимная горутина). Переменная i была одна на весь цикл.

Когда горутины реально начинали выполняться (после небольшой задержки time.Sleep), цикл for, скорее всего, уже завершался. К этому моменту переменная i имела бы свое конечное значение (в данном случае 3, так как цикл завершается, когда i становится равным count).

Поэтому в старых версиях Go этот код, скорее всего, вывел бы:
Запуск горутин...
Ожидание завершения горутин...
Горутина видит i = 3
Горутина видит i = 3
Горутина видит i = 3

Все горутины завершены.

(Порядок строк "Горутина видит..." мог быть разным). Чтобы обойти это, раньше приходилось делать так: i := i внутри цикла перед запуском горутины, чтобы создать копию переменной для каждой итерации.

Новое поведение (Go 1.22 и новее, включая 1.24): Начиная с Go 1.22, семантика переменных цикла for была изменена для предотвращения этой распространенной ошибки. Теперь переменная цикла (i в нашем случае) пересоздается для каждой итерации.

Это означает, что каждая горутина захватывает свою собственную копию i, соответствующую значению на момент этой итерации.

Ожидаемый вывод (Go 1.24): Благодаря изменению в Go 1.22, каждая горутина теперь корректно видит значение i той итерации, в которой она была запущена. Поэтому вывод будет (порядок строк "Горутина видит..." может варьироваться из-за недетерминированного планирования горутин):
Запуск горутин...
Ожидание завершения горутин...
Горутина видит i = 0
Горутина видит i = 1
Горутина видит i = 2
Все горутины завершены.
Use code with caution.
Или, например:
Запуск горутин...
Ожидание завершения горутин...
Горутина видит i = 2
Горутина видит i = 0
Горутина видит i = 1
Все горутины завершены.


Подвох заключается в том, что код выглядит как классический пример ошибки захвата переменной цикла, но из-за изменений в языке начиная с Go 1.22, он теперь работает "правильно" без необходимости явного копирования переменной (i := i).
  • 🔥 9
  • 👍 7
  • 🥰 3
  • 👎 1
More from @golangtests
  1. Sep 26, 2026Nil-интерфейс в Go: почему err == nil выдаёт false? Nil-интерфейс в Go ломает даже опытных…
  2. Sep 23, 2026Задача по Go: `select` и побочные эффекты Что выведет программа? package main import "fmt"…
  3. Sep 23, 2026Не застревайте в прошлом — учитесь новым навыкам и ИИ Чтобы успевать за требованиями рынка…
  4. Sep 13, 2026👣 Задача на Go 1.27: concurrent job scheduler с generic methods Нужно реализовать библиот…
  5. Sep 13, 2026🔥 Хочешь расти в IT быстрее остальных? Перестань учиться в одиночку Можно годами смотреть…
  6. Sep 2, 2026👣 Go 1.27: почему две разные closure внезапно могут иметь одинаковый адрес функции? Что в…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →