Golang: For-loop Concurrency Quirk
Here’s a quick problem which implicitly shares a variables to a goroutines by reference. We’re using a WaitGroup to prevent the main() Goroutine exiting before the loop finishes.
Why we are getting 10 when we have conditioned the loop to 9 may be obvious to some but it’s easy to be caught off guard.
A common mistake is to assume that this has something to do with function closures or some advanced scheduling mechanism. Let’s first take a look at for loops as our beef is with the value defying logic.
For loop
A `for` loop is made of 3 main components (there’s more but let’s not complicate things)
- The
for
keyword - The e.g.
i := 0; i < foo; i--
statement - The
{}
body (execution context)
Breakdown
- The goroutines queue for scheduling on each loop.
- Meanwhile the
for
loop finishes before the goroutines schedule and execute. - On the last execution of the for loop’s body the for-statement is oblivious to when it has to stop. So when
i
is equal to 9 it must still follow the incremental rulei++
. Now thati
is equal to 10, the conditional rulei < 10
results to false which is what stops the loop from executing the body further. - This behaviour is not specific to Go, it applies to all C based programming languages.
We can see similar behaviour in JavaScript. setTimeout
is an asynchronous function. In the below code setTimeout is added to the call-stack on each iteration and executes after the loop completes.
But this is not the entire picture.
In Golang, the iterative variable is shared to the body as a pointer.
In JavaScript notice I’m using the old
var
syntax for declaring a variable. This is becausevar
in JavaScript behaves similarly-ish to var declarations in Golang. The iterative variable is treated like a pointer. On the other hand thelet
keyword copies the value from body to statement on each iteration which gives us 0,1,2,3,4,5… (setTimeout is async but not concurrent)
We can solve this in Golang by simply copying by value.
The goal is not to print in order, we’re trying to do things concurrently so we expect a sporadic occurrence of numbers from 0 to 9.