ProgrammazioneBackend разработчик

Как в Go реализованы замыкания (closures), какие подводные камни связаны с их использованием при запуске горутин внутри циклов, и как избежать типичных ошибок?

Supera i colloqui con l'assistente IA Hintsage

Ответ

В Go замыкания (closures) — это функции, которые "замыкают" (захватывают) переменные своей окружающей области видимости. Чаще всего замыкания используются для анонимных функций, созданных внутри других функций.

Наиболее типичная проблема при работе с замыканиями — неожиданное поведение при использовании переменных цикла внутри горутины:

for i := 0; i < 3; i++ { go func() { fmt.Println(i) }() }

Каждая горутина может напечатать одно и то же значение i, потому что переменная i циклит, и замыкание захватывает именно переменную, а не её значение на каждой итерации.

Правильный способ:

for i := 0; i < 3; i++ { go func(val int) { fmt.Println(val) }(i) }

Такое поведение связано с тем, что замыкание держит ссылку на переменную (её адрес), а не её срезанное (by value) значение.

Вопрос с подвохом

Какое значение напечатают несколько запущенных горутин внутри цикла, если они захватывают переменную цикла?

Ответ: Все горутины могут напечатать одно и то же значение (часто последнее), так как они видят текущее значение переменной, а к моменту выполнения горутины цикл уже завершился. Чтобы избежать этого, переменную нужно передавать как параметр в замыкание.

Пример:

for i := 0; i < 5; i++ { go func() { fmt.Println(i) }() } // вероятнее всего получим: 5 5 5 5 5

Примеры реальных ошибок из-за незнания тонкостей темы


История

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

История

В облачном сервисе бэкенд Go-интеграций команды решили оптимизировать сбор метрик, запускав их обработку в цикле с помощью анонимных функций — внутри горутины захватили индекс map, результат: часть обработчиков собирала данные не для своих сервисов, а для последнего обработанного индекса.

История

В курьерском стартапе некорректное использование замыканий при обновлении координат заказа приводило к тому, что массово обновлялись координаты последнего заказа в срезе, а не текущего — из-за гонки при обращении к срезу внутри анонимной функции.