看一段代码:
package main import ( "fmt" "runtime" "sync" ) var ( counter int wg sync.WaitGroup ) func main() { wg.Add(2) go incCounter(1) go incCounter(2) wg.Wait() fmt.Println("Final Counter:", counter) } func incCounter(id int) { defer wg.Done() for count := 0; count < 2; count++ { value := id runtime.Gosched() value++ counter = value } }
goroutine执行的是副本值,然后将副本值写入counter,所以在切换goroutine时,goroutine中的值会覆盖counter。其中Gosched函数是runtime包中用于将goroutine从当前线程退出,给其它goroutine运行的机会。这段代码执行下来理论上应该是存在竞争状态的,对于counter这个变量,在两个goroutine的切换下,一共加了4次,但是由于每次切换后进入队列的并不是真的这个值,而是一个副本,结果输出应该为2。
事实貌似是这样。。。貌似有点小问题。。。
检测竞争状态,再把这个gosched函数注释,然后重新检测竞争状态,先后编译执行得到的是:
为什么会出现这个情况呢?Final Counter: 3
================== WARNING: DATA RACE Write at 0x0000005b73c0 by goroutine 6: main.incCounter() /home/qmq/gopath/src/github.com/goinaction/code/test.go:49 +0x74 Previous write at 0x0000005b73c0 by goroutine 7: main.incCounter() /home/qmq/gopath/src/github.com/goinaction/code/test.go:49 +0x74 Goroutine 6 (running) created at: main.main() /home/qmq/gopath/src/github.com/goinaction/code/test.go:25 +0x68 Goroutine 7 (running) created at: main.main() /home/qmq/gopath/src/github.com/goinaction/code/test.go:26 +0x89 ================== Final Counter: 2 Found 1 data race(s)
================== WARNING: DATA RACE Write at 0x0000005b73c0 by goroutine 7: main.incCounter() /home/qmq/gopath/src/github.com/goinaction/code/test.go:49 +0x74 Previous write at 0x0000005b73c0 by goroutine 6: main.incCounter() /home/qmq/gopath/src/github.com/goinaction/code/test.go:49 +0x74 Goroutine 7 (running) created at: main.main() /home/qmq/gopath/src/github.com/goinaction/code/test.go:26 +0x89 Goroutine 6 (finished) created at: main.main() /home/qmq/gopath/src/github.com/goinaction/code/test.go:25 +0x68 ================== Final Counter: 3 Found 1 data race(s)================
输出小概率有3的情况,可能是goroutine没有退出,所以发生了新goroutine中的值与上一次goroutine副本值相加的情况。对于这样存在多个goroutine对一个共享资源进行操作的功能还是需要对其加锁,或使用简单的atomic。