[Solved] Unexpected behavior from launching a method call on a loop variable as a goroutine


Problem

Your first version has a synchronisation bug, which manifests itself as a data race:

$ go run -race main.go
0 (PrintAddr): 0xc0000b4018
0 (PrintAddr): 0xc0000c2120
==================
WARNING: DATA RACE
Write at 0x00c0000b4018 by main goroutine:
  main.main()
      redacted/main.go:29 +0x1e5

Previous read at 0x00c0000b4018 by goroutine 7:
  main.(*User).PrintAddr()
      redacted/main.go:19 +0x44

Goroutine 7 (finished) created at:
  main.main()
      redacted/main.go:30 +0x244
==================
1 (PrintAddr): 0xc0000b4018
1 (PrintAddr): 0xc0000c2138
2 (PrintAddr): 0xc0000b4018
2 (PrintAddr): 0xc0000c2150
3 (PrintAddr): 0xc0000b4018
3 (PrintAddr): 0xc0000c2168
Found 1 data race(s)

The for loop (line 29) keeps updating loop variable user while (i.e. in a concurrent manner without proper synchronisation) the PrintAddr method accesses it via its pointer receiver (line 19). Note that if you don’t start user.PrintAddr() as a goroutine on line 30, the problem goes away.

The problem and a solution to it are actually given at the bottom of the Wiki you link to.

But why did the situation in the article not apply here and I didn’t get many 3 (PrintAddr) instead?

That synchronisation bug is a source of undesired undeterminism. In particular, you cannot predict how many times (if any) 3 (PrintAddr) will be printed, and that number may vary from one execution to the next. In fact, scroll up and see for yourself: in my execution with the race detector on, the output happened to feature two of each integer between 0 and 3, despite the bug; but there’s no guarantee for that.

Solution

Simply shadow loop variable user at the top of the loop and the problem goes away:

for i, user := range users {
    user := user // <---
    go user.PrintAddr()
    go users[i].PrintAddr()
}

PrintAddr will now operate on the innermost user variable, which is not updated by the for loop on line 29.

(Playground)

Addendum

You should also use a wait group to wait for all your goroutines to finish. time.Sleep is no way to coordinate goroutines.

1

solved Unexpected behavior from launching a method call on a loop variable as a goroutine