Горутина — это эффективный и легковесный механизм многопоточного выполнения, популярный среди разработчиков 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
Читайте также:
Перевод статьи Pranay Singhal: A Goroutines Gotcha