Konstantin Milyutin
Konstantin Milyutin

Reputation: 12356

Observing invalid value inside the loop

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?

Upvotes: 1

Views: 76

Answers (3)

elricfeng
elricfeng

Reputation: 394

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.

Upvotes: -1

peterSO
peterSO

Reputation: 166559

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()
            }()
        }
    }
}

Go: Data Race Detector

Upvotes: 3

icza
icza

Reputation: 417512

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.

Upvotes: 1

Related Questions