A Goroutines Gotcha

Pranay Singhal
The Startup
Published in
3 min readAug 19, 2019
Image Source: Ashley McNamara

Goroutines are a simple and lightweight concurrency mechanism that is very popular among Golang developers. It is common for Golang programmers to leverage Goroutine semantics to achieve program execution efficiency through parallelism. However, care must be taken when designing Goroutine implementations in order to avoid unintended side-effects. I will highlight one such side effect that I ran into while writing my own Goroutines. As it turns out, this was a documented pitfall that I wasn’t aware of until I ran into it myself.

The program

The logic I needed to code was simple: I had a slice of functions of a certain type that I needed to iterate over and execute. Since these functions were independent of each other, it only made sense to execute each function in its own Goroutine. Simple enough. The code I came up with was similar to the following:

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” is a variadic function that takes any number of functions as input, and iterates over these to execute them. The execution of each function is performed in a separate Goroutine. So, what would you expect the above program to print when it is run? “I am function func1” and “I am function func2” (not necessarily in that order), right?

Wrong!

When run , the above program prints the following:

I am function func2
I am function func2

The pitfall

So, what happened to func1? Why didn’t it get executed?

The explanation lies in this code section:

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

Each iteration of the loop creates an anonymous Goroutine closure function, which uses the same shared variable “fn” from the parent loop. The Goroutines are executed asynchronously, so the function that is actually executed by each Goroutine is the one that is populated in the “fn” variable at the time of execution. In most instances, the outer loop terminates before any of the Goroutines is executed, so “fn” is pointing to “func2”. Therefore, both the Goroutines end up executing func2.

The fix

Since the root cause of the issue is the shared “fn” variable, it can be solved by adjusting the above code section as illustrated below:

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

Instead of referring to the “fn” variable directly, the Goroutine closure now accepts the function to execute as a parameter, thus creating a copy of the function passed to the Goroutine closure in each iteration. This ensures that each Goroutine will refer to the function that “fn” was pointing to when it was set up.

With this small change, the above program prints the expected output:

I am function func2
I am function func1

Read more about this interesting Goroutines pitfall at https://golang.org/doc/faq#closures_and_goroutines

--

--