I've stumbled on a buggy Golang code that was trying to use mutex to prevent changes to the variables printed in a goroutine:
runtime.GOMAXPROCS(1)
mutex := new(sync.Mutex)
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
mutex.Lock()
go func() {
fmt.Println(i, j, i + j);
mutex.Unlock()
}()
}
}
It is clear to me that mutex doesn't lock directly, but on the next iteration, when the value has been incremented already. What is not clear is why j variable reaches 10 according to the output:
...
0 7 7
0 8 8
0 9 9
1 10 11 <--- isn't supposed to be here
...
1 9 10
2 10 12
...
I tried to debug the code and j = 10
is printed when the outer loop for i increments its value. It looks as if the outer loop was releasing the thread allowing goroutine to execute and see invalid value of 10. Could someone clarify this behavior?
You have data race, so the results are undefined. Run it with the -race
option to see.
When you call mutex.Lock()
inside the loop body first, that doesn't block. Then you launch a goroutine that reads i
and j
and the main goroutine continues to the next iteration of the inner loop, and increments j
. Then calls lock again, which will block until the previous goroutine finishes.
But you already have an unsynchronized access (read and write) to j
.
You have data races. The results are undefined.
$ go run -race racer.go
==================
WARNING: DATA RACE
Read at 0x00c000016110 by goroutine 7:
main.main.func1()
/home/peter/gopath/racer.go:17 +0x7f
Previous write at 0x00c000016110 by main goroutine:
main.main()
/home/peter/gopath/racer.go:14 +0xf1
Goroutine 7 (running) created at:
main.main()
/home/peter/gopath/racer.go:16 +0xcd
==================
0 1 1
0 2 2
0 3 3
0 4 4
0 5 5
0 6 6
0 7 7
0 8 8
0 9 9
==================
WARNING: DATA RACE
Read at 0x00c000016108 by goroutine 16:
main.main.func1()
/home/peter/gopath/racer.go:17 +0x50
Previous write at 0x00c000016108 by main goroutine:
main.main()
/home/peter/gopath/racer.go:13 +0x140
Goroutine 16 (running) created at:
main.main()
/home/peter/gopath/racer.go:16 +0xcd
==================
1 10 11
1 1 2
1 2 3
1 3 4
1 4 5
1 5 6
1 6 7
1 7 8
1 8 9
1 9 10
2 10 12
2 1 3
2 2 4
2 3 5
2 4 6
2 5 7
2 6 8
2 7 9
2 8 10
2 9 11
3 10 13
3 1 4
3 2 5
3 3 6
3 4 7
3 5 8
3 6 9
3 7 10
3 8 11
3 9 12
4 10 14
4 1 5
4 2 6
4 3 7
4 4 8
4 5 9
4 6 10
4 7 11
4 8 12
4 9 13
5 10 15
5 1 6
5 2 7
5 3 8
5 4 9
5 5 10
5 6 11
5 7 12
5 8 13
5 9 14
6 10 16
6 1 7
6 2 8
6 3 9
6 4 10
6 5 11
6 6 12
6 7 13
6 8 14
6 9 15
7 10 17
7 1 8
7 2 9
7 3 10
7 4 11
7 5 12
7 6 13
7 7 14
7 8 15
7 9 16
8 10 18
8 1 9
8 2 10
8 3 11
8 4 12
8 5 13
8 6 14
8 7 15
8 8 16
8 9 17
9 10 19
9 1 10
9 2 11
9 3 12
9 4 13
9 5 14
9 6 15
9 7 16
9 8 17
9 9 18
Found 2 data race(s)
exit status 66
$
racer.go
:
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
runtime.GOMAXPROCS(1)
mutex := new(sync.Mutex)
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
mutex.Lock()
go func() {
fmt.Println(i, j, i+j)
mutex.Unlock()
}()
}
}
}
Let me to answer why you can get the impossible 10 when print j
.
Because when you use goroutine in a loop, fmt.Println(i, j, i+j)
race with i++/j++
, you cannot determine what value exactly when you print out, and if j
increase to the bound, it's possible to print out 10.
if you want to prevent from this race, you can transfer i
, j
as argument values, e.g.
runtime.GOMAXPROCS(1)
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
go func(a, b, c int) {
fmt.Println(a, b, c);
}(i, j, i+j)
}
}
Hope this helps.