Ловушка для горутины


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

Программа

Логика программы проста: есть набор функций определенного типа, который нужно перебрать и выполнить. Каждая из функций независима, поэтому удобно использовать индивидуальные горутины. Код имеет следующий вид:

import ( "fmt" "sync" ) func main() { ConcurrentFunctions(func1, func2) } func ConcurrentFunctions(fns ...func()) { var wg sync.WaitGroup for _, fn := range fns { wg.Add(1) go func() { fn() wg.Done() }() } wg.Wait() } func func1() { fmt.Println("I am function func1") } func func2() { fmt.Println("I am function func2") }

“ConcurrentFunctions” — это функция с переменным количеством аргументов, которая принимает на вход любое число функций, перебирает и реализует их. Реализация каждой функции происходит за счёт отдельной горутины. Согласно логике, вывод должен был быть “I am function func1” и “I am function func2”, но это не так.

I am function func2 I am function func2 Ошибка

Загвоздка кроется в этой части кода:

for _, fn := range fns { wg.Add(1) go func() { fn() wg.Done() }() }

Каждая итерация цикла создавала анонимную функцию замыкания с горутиной, использующей ту же переменную “fn”, что и родительский цикл. Горутины выполняются асинхронно, поэтому для каждой из них в момент использования актуальна переменная “fn”. В большинстве случаев внешний цикл завершался до реализации какой-либо горутины, поэтому “fn” указывает на “func2”. Таким образом, обе горутины замыкаются на func2.

Решение

Поскольку причиной ошибки стала переменная “fn”, проблему может решить изменение следующей секции кода:

for _, fn := range fns { wg.Add(1) go func(f func()) { f() wg.Done() }(fn) }

Вместо обращения к переменной “fn” напрямую, механизм замыкания горутины принимает функцию в качестве параметра, при этом копируя её для каждой из итераций горутин.

С этим небольшим изменением выходные данные приняли следующий вид:

I am function func2 I am function func1

Перевод статьи


Поделиться статьей:


Вернуться к статьям

Комментарии

    Ничего не найдено.