The error in the Go snippet is that "v" is a single memory location shared throughout the loop.
The closure doesn't get a copy, so what usually ends up happening is that every goroutine gets the last value (since the goroutines are probably not scheduled until the end of the loop).
This problem doesn't just affect loops, of course: A goroutine can access any local environment outside its scope, and local variables can mutate.
The worst surprise I ever encountered was this (simplified):
func makeWorkersDocomplicatedStuff()
ch := make(chan string)
defer func() {
if ch != nil {
close(ch)
}
}
for i := 0; i < numWorkers; i++ {
go func() {
for {
select {
case s := <-ch:
// ...
}
}
}
}
close(ch)
ch = nil
// ... More stuff ...
}
What will happen here is that the goroutines will all block forever, because "ch" becomes nil, and in Go, polling a nil channel will block forever (it's an interesting design choice in such a strict language).
Rewriting and refactoring this was trivial enough, but catching it was time wasted. Lessons learned: (1) Be scrupulous about closure environments, (2) be super careful about nil channels, and (3) try to avoid defers whose concerns don't fully encapsulate the function body (so defers in the middle of a function is often code smell).