Golang

Горутина — это эффективный и легковесный механизм многопоточного выполнения, популярный среди разработчиков 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