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

huangapple go评论87阅读模式
英文:

Different behavior of updating global variable in goroutine

问题

我有一个如下的Go程序。它启动NumberOfCPUs-1个goroutine,在每个goroutine中只更新全局变量x。输出结果是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)
}

如果我稍微修改一下程序,像这样:

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的值将会是一个随机的大数。

我认为这可能与goroutine调度器有关。在第一种情况下,goroutine的数量少于CPU核心的数量,因此main函数可以在所有现有的goroutine中执行。因为没有系统调用、I/O或通道通信发生在每个goroutine内部,所以goroutine调度器不会工作。而且由于goroutine没有被中断,更新后的x没有机会被写回。

而在第二种情况下,goroutine的数量等于CPU核心的数量,为了让main函数有机会运行,我在更新x之后加了一个time.Sleep(0)。我认为每次goroutine调度器切换goroutine时,更新后的x将被写回到它的原始内存位置。

有人可以确认我的想法吗?有什么遗漏的地方吗?

谢谢。

英文:

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 &lt; threads; i++ {
        go func() {
            for {
            	x++
            }
        }()
    }
    time.Sleep(time.Second)
    fmt.Println(&quot;x =&quot;, x)
}

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

func main() {
    var x int
    threads := runtime.GOMAXPROCS(0)
    for i := 0; i &lt; threads; i++ {
        go func() {
            for {
            	x++
            	time.Sleep(0)
            }
        }()
    }
    time.Sleep(time.Second)
    fmt.Println(&quot;x =&quot;, 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.

答案1

得分: 6

你有多个goroutine共享同一个变量x,并且存在未同步的读写操作。这会导致数据竞争,因此x的结果是不确定的。你可以使用-race选项来运行竞争检测器。详见《Go竞争检测器介绍》


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

输出:

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

输出:

$ 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
英文:

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 (
	&quot;fmt&quot;
	&quot;runtime&quot;
	&quot;time&quot;
)

func main() {
	var x int
	threads := runtime.GOMAXPROCS(0) - 1
	for i := 0; i &lt; threads; i++ {
		go func() {
			for {
				x++
			}
		}()
	}
	time.Sleep(time.Second)
	fmt.Println(&quot;x =&quot;, 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 (
	&quot;fmt&quot;
	&quot;runtime&quot;
	&quot;time&quot;
)

func main() {
	var x int
	threads := runtime.GOMAXPROCS(0)
	for i := 0; i &lt; threads; i++ {
		go func() {
			for {
				x++
				time.Sleep(0)
			}
		}()
	}
	time.Sleep(time.Second)
	fmt.Println(&quot;x =&quot;, 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

huangapple
  • 本文由 发表于 2017年8月10日 15:23:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/45607048.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定