在goroutine中更新全局变量的不同行为

I have one go program as below. It starts NumberOfCPUs-1 goroutines and inside each goroutine just update global variable x. Output is x = 0.

func main() {
    var x int
    threads := runtime.GOMAXPROCS(0)-1
    for i := 0; i < threads; i++ {
        go func() {
            for {
                x++
            }
        }()
    }
    time.Sleep(time.Second)
    fmt.Println("x =", x)
}

If I change the program a little bit, like this:

func main() {
    var x int
    threads := runtime.GOMAXPROCS(0)
    for i := 0; i < threads; i++ {
        go func() {
            for {
                x++
                time.Sleep(0)
            }
        }()
    }
    time.Sleep(time.Second)
    fmt.Println("x =", x)
}

x will be some random big value.

I think it may related to goroutine scheduler. In first case, number of goroutines is less than number of cpu cores, so that main func can be executed with all existing goroutines. Because no syscall, I/O, or channel communication happen inside each goroutine, goroutine scheduler won't work. And because goroutines are not interrupted, updated x doesn't have chance to be written back.

While in second case, number of goroutines is equal to number of cpu cores, in order to let main func have chance to run, I put a time.Sleep(0) after update x. I think every time goroutine scheduler switches goroutines, updated x will be written back to its original memory place.

Could anyone confirm my thoughts? Is there anything missed?

Thanks.

You have multiple goroutines sharing the same variable x with unsynchronized reads and writes. You have data races. Therefore, your results for x are undefined. Use option -race to run the race detector. See Introducing the Go Race Detector.


package main

import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    var x int
    threads := runtime.GOMAXPROCS(0) - 1
    for i := 0; i < threads; i++ {
        go func() {
            for {
                x++
            }
        }()
    }
    time.Sleep(time.Second)
    fmt.Println("x =", x)
}

Output:

$ go run -race race1.go
==================
WARNING: DATA RACE
Read at 0x00c420084010 by goroutine 7:
  main.main.func1()
      /home/peter/gopath/src/race1.go:15 +0x3b

Previous write at 0x00c420084010 by goroutine 6:
  main.main.func1()
      /home/peter/gopath/src/race1.go:15 +0x54

Goroutine 7 (running) created at:
  main.main()
      /home/peter/gopath/src/race1.go:13 +0xb6

Goroutine 6 (running) created at:
  main.main()
      /home/peter/gopath/src/race1.go:13 +0xb6
==================
x = 24717968
Found 1 data race(s)
exit status 66

package main

import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    var x int
    threads := runtime.GOMAXPROCS(0)
    for i := 0; i < threads; i++ {
        go func() {
            for {
                x++
                time.Sleep(0)
            }
        }()
    }
    time.Sleep(time.Second)
    fmt.Println("x =", x)
}

Output:

$ go run -race race2.go
==================
WARNING: DATA RACE
Read at 0x00c4200140d0 by goroutine 7:
  main.main.func1()
      /home/peter/gopath/src/race2.go:15 +0x3b

Previous write at 0x00c4200140d0 by goroutine 6:
  main.main.func1()
      /home/peter/gopath/src/race2.go:15 +0x54

Goroutine 7 (running) created at:
  main.main()
      /home/peter/gopath/src/race2.go:13 +0xb3

Goroutine 6 (running) created at:
  main.main()
      /home/peter/gopath/src/race2.go:13 +0xb3
==================
==================
WARNING: DATA RACE
Read at 0x00c4200140d0 by goroutine 8:
  main.main.func1()
      /home/peter/gopath/src/race2.go:15 +0x3b

Previous write at 0x00c4200140d0 by goroutine 6:
  main.main.func1()
      /home/peter/gopath/src/race2.go:15 +0x54

Goroutine 8 (running) created at:
  main.main()
      /home/peter/gopath/src/race2.go:13 +0xb3

Goroutine 6 (running) created at:
  main.main()
      /home/peter/gopath/src/race2.go:13 +0xb3
==================
x = 14739962
Found 2 data race(s)
exit status 66