От чего зависит захваченное замыканием значение переменной цикла при запуске горутин?
Зависит от версии языка Go и области объявления переменной. В модуле с версией Go 1.22 или новее переменная, объявленная самим циклом, получает отдельную сущность на каждой итерации, поэтому замыкания видят свои значения. В более старых версиях такие замыкания обычно разделяют одну переменную цикла и могут наблюдать изменяющееся или итоговое значение.
Ранее переменная цикла переиспользовалась между итерациями. Замыкание захватывает переменную, а не снимок её текущего значения, поэтому отложенное выполнение горутины часто приводило к неожиданным результатам.
Начиная с Go 1.22, для переменных, объявленных циклом, введена семантика отдельных переменных на итерацию. Это устраняет распространённую категорию ошибок без необходимости вручную передавать значение в анонимную функцию.
Горутина может начать выполняться не сразу после оператора запуска. К этому моменту цикл способен уже изменить переменную, а замыкание в старой семантике прочитает именно общую переменную, а не значение, которое было у неё при создании.
Нужно учитывать и область объявления: если переменная создана вне цикла и только изменяется внутри него, переход на Go 1.22 не делает её отдельной для каждой итерации. Ошибка может привести к обработке неправильных идентификаторов, повторной обработке одного элемента или гонке данных.
В Go 1.22 и новее переменная, объявленная в заголовке цикла через короткое объявление, логически привязана к конкретной итерации. Замыкание, созданное на этой итерации, захватывает соответствующую переменную. Порядок вывода при этом не гарантируется: отдельные значения не означают последовательное выполнение горутин.
В старом режиме все замыкания могли обращаться к одной переменной цикла. Передача значения параметром анонимной функции остаётся универсальным явным решением: аргумент вычисляется в момент запуска горутины, после чего функция работает с собственной копией параметра.
В модуле с Go 1.22 или новее в канал попадут значения 0, 1 и 2 в произвольном порядке. Для старого режима тот же код не даёт такой гарантии: замыкания используют общую переменную цикла. Явная передача параметра делает намерение очевидным и полезна, когда код должен работать одинаково независимо от версии семантики цикла.
Даже при корректном захвате переменной остаются обычные правила конкурентности: чтение и запись общей памяти без синхронизации могут образовать гонку. Канал, мьютекс или другой механизм синхронизации нужен для безопасного обмена общими данными, но не для самого факта передачи значения параметру.
Сервис параллельно отправляет запросы для элементов списка. После обновления версии Go часть кода начинает полагаться на новые правила захвата переменных цикла, но тот же пакет может собираться в проектах со старым значением директивы версии модуля.
Вариант с прямым захватом переменной короток, но зависит от версии семантики и хуже читается при ревью. Вариант с созданием локальной копии внутри тела цикла совместим со старым поведением, но добавляет шаблонный код. Вариант с передачей значения параметром явно фиксирует границу данных и обычно является самым переносимым решением.
Практический выбор — передавать элемент параметром, если пакет поддерживает разные версии Go или корректность зависит от точного значения. Это исключает двусмысленность захвата; после такого изменения остаётся только отдельно решить вопросы ограничения числа горутин, обработки ошибок и ожидания завершения.
Нет. Важна семантика, применяемая к конкретному модулю, в том числе его директива версии Go. Современный компилятор может сохранять старое поведение для модуля, который заявляет более старую версию языка, поэтому нужно смотреть не только на версию инструмента сборки.
Нет. Новые правила исправляют захват данных, но не планирование горутин. Значения 0, 1 и 2 могут прийти в любом порядке; если порядок важен, его нужно задать отдельным протоколом, например последовательной обработкой, индексированием результатов или координацией через каналы.
Нет. Если одна переменная находится за пределами цикла и горутины одновременно читают её, пока другая часть программы записывает новое значение, возникает гонка данных. Новая семантика касается переменных, создаваемых циклом, поэтому для внешней переменной требуются копия значения, передача параметром или синхронизация.