Найти в Дзене

#1 Самая популярная задача по Go

В коде используются горутины для вывода значений i. Что выведется? Отвечать на этот вопрос надо так: вывод недетерменирован. Это, если кратко, а подробный ответ будет таким: Когда основная горутина - функция main запускает горутину с анонимной функцией, она не ждет, пока та завершится, и продолжает свое выполнение. В большинстве случаев, вывод будет пустым, так как main закончит выполнение до запуска горутин из цикла. Исправляем: 1. В 9 строке создали экземпляр WaitGroup для синхронизации горутин. WaitGroup используем для гарантии, что все горутины завершат свою работу до того, как завершится main. 2. С помощью wg.Add(5), установили счетчик WaitGroup на 5. У нас ведь 5 горутин. 3. В цикле запустили 5 горутин. В каждой использовали wg.Done(), который уменьшает счетчик WaitGroup на 1. Использование defer гарантирует, что wg.Done() будет вызвано при любом завершении функции, независимо от того, произошла ошибка или нет. 4. В конце вызвали wg.Wait(). Это блокирует выполнение программы д

В коде используются горутины для вывода значений i. Что выведется?

Отвечать на этот вопрос надо так: вывод недетерменирован. Это, если кратко, а подробный ответ будет таким:

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

Исправляем:

-2

1. В 9 строке создали экземпляр WaitGroup для синхронизации горутин. WaitGroup используем для гарантии, что все горутины завершат свою работу до того, как завершится main.

2. С помощью wg.Add(5), установили счетчик WaitGroup на 5. У нас ведь 5 горутин.

3. В цикле запустили 5 горутин. В каждой использовали wg.Done(), который уменьшает счетчик WaitGroup на 1. Использование defer гарантирует, что wg.Done() будет вызвано при любом завершении функции, независимо от того, произошла ошибка или нет.

4. В конце вызвали wg.Wait(). Это блокирует выполнение программы до тех пор, пока счетчик WaitGroup не станет равным 0, что означает завершение всех горутин.

Мы решили одну из проблем!

Но есть вторая - при запуске кода будет выведено пять пятерок - это не то, что мы ожидали, нам надо вывести цифры от 0 до 4 (в любом порядке). Такая ошибка называется loop variable i captured by func literal.

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

Представьте следующий сценарий:

1. Значение i равно 0. Запускается горутина.

2. Основной поток увеличивает значение i до 1.

3. Горутина, запущенная на шаге 1, начинает выполняться и выводит значение i, которое теперь равно 1, а не 0.

Теперь, если у нас есть 5 горутин, запущенных таким образом, их вывод может быть абсолютно любым, например: 5, 5, 5, 5, 5 или 3, 4, 5, 5, 5 и так далее. Все горутины ссылаются на одну и ту же переменную i, и их выполнение может начаться в любой момент времени, когда i может иметь любое значение от 0 до 5.

на собесе могут спросить, почему выводится цифра 5, ведь i в цикле должно быть меньше 5, то есть от 0 до 4.
Когда цикл for i := 0; i < 5; i++ завершается, значение i устанавливается равным 5, потому что это значение, при котором условие цикла становится ложным и цикл завершается. Если горутины начинают выполняться после того, как основной поток программы выходит из цикла, все они увидят i равным 5.

Исправляем:

-3

Передаем текущее значение i как аргумент в анонимную функцию, чтобы каждая горутина имела свою копию значения i:

1. Поменяли сигнатуру анонимной функции-горутины, добавив j int, ее же используем в Println(j)

2. На вход анонимной функции передаем i

Есть второй способ решения этой проблемы - заново инициализировать переменную i в цикле:

-4

P.S. в версии языка 1.22 планируют исправить логику захвата переменной в цикле, и вторая проблема отпадет сама собой.