我试图用2个goroutines编写示例程序来说明比赛条件。一次打印x
,另一次打印x
。
package main
import (
"fmt"
"time"
)
func main() {
x := 0
go func() {
for {
if x%2 == 0 {
fmt.Println(x)
}
}
}()
go func() {
for {
x = x + 1
}
}()
<-time.After(1 * time.Second)
fmt.Println("final", x)
}
但是增量例程什么也不做,最后x
保持为零。但是,如果递增例程添加fmt.Println(x)
,它将开始递增x
并按预期运行程序。
更新#1:
此简单示例的目的仅是说明数据争用问题。我知道在不同步的情况下读取两个goroutine之间的共享变量是一个坏主意。请停下来向我指出。我的问题是为什么增量goroutine在这里什么都不做?在我的世界图中,应该稳定增加,但是问题应该出现在无法确定结果的读取程序中,因为读取x
是一个,而打印x
时可能是另一个。
更新#2:
我有另一个例子:
package main
import (
"time"
"fmt"
)
func main() {
x := 0
go func() {
last := x
for {
current := x
if last != current {
last = current
fmt.Println(last)
}
}
}()
go func() {
for {
//<-time.After(1 * time.Second) // if uncomment this row program start do "something"
x = x + 1
}
}()
<-time.After(10 * time.Second)
fmt.Println("final", x)
}
该示例非常简单。一个例行增量计数器,如果更改则另打印。仅当写入x = x+1
并读取current := x
时,该程序才具有未定义的行为,它不是原子操作,并且一个线程在写入过程中开始在另一个线程中读取变量,而另一个操作则读取已损坏值。
但是,为什么在这种情况下,当增量例程程序中的非注释睡眠开始起作用时?
答案 0 :(得分:0)
您有数据竞争:Go Data Race Detector。因此,您的结果是不确定的。
package main
import (
"fmt"
"time"
)
func main() {
x := 0
go func() {
for {
if x%2 == 0 {
//fmt.Println(x)
}
}
}()
go func() {
for {
x = x + 1
}
}()
<-time.After(1 * time.Second)
fmt.Println("final", x)
}
输出:
$ go run -race racer.go > /dev/null
==================
WARNING: DATA RACE
Write at 0x00c000088010 by goroutine 7:
main.main.func2()
/home/peter/gopath/src/racer.go:21 +0x4e
Previous read at 0x00c000088010 by goroutine 6:
main.main.func1()
/home/peter/gopath/src/racer.go:13 +0x38
Goroutine 7 (running) created at:
main.main()
/home/peter/gopath/src/racer.go:19 +0x9c
Goroutine 6 (running) created at:
main.main()
/home/peter/gopath/src/racer.go:11 +0x7a
==================
Found 1 data race(s)
exit status 66
$
package main
import (
"fmt"
"time"
)
func main() {
x := 0
go func() {
for {
if x%2 == 0 {
fmt.Println(x)
}
}
}()
go func() {
for {
x = x + 1
}
}()
<-time.After(1 * time.Second)
fmt.Println("final", x)
}
输出:
$ go run -race racer.go > /dev/null
==================
WARNING: DATA RACE
Read at 0x00c00008c010 by goroutine 6:
main.main.func1()
/home/peter/gopath/src/racer.go:13 +0x3c
Previous write at 0x00c00008c010 by goroutine 7:
main.main.func2()
/home/peter/gopath/src/racer.go:21 +0x4e
Goroutine 6 (running) created at:
main.main()
/home/peter/gopath/src/racer.go:11 +0x7a
Goroutine 7 (running) created at:
main.main()
/home/peter/gopath/src/racer.go:19 +0x9c
==================
==================
WARNING: DATA RACE
Write at 0x00c00008c010 by goroutine 7:
main.main.func2()
/home/peter/gopath/src/racer.go:21 +0x4e
Previous read at 0x00c00008c010 by goroutine 6:
main.main.func1()
/home/peter/gopath/src/racer.go:13 +0x3c
Goroutine 7 (running) created at:
main.main()
/home/peter/gopath/src/racer.go:19 +0x9c
Goroutine 6 (running) created at:
main.main()
/home/peter/gopath/src/racer.go:11 +0x7a
==================
^Csignal: interrupt
$
答案 1 :(得分:0)
您看不到任何事情发生,因为编译器很聪明并且可以优化增量。因为没有同步,所以编译器可以有效地看到以下代码:
x := 0
for { x = x + 1 }
由于x
从未在任何地方真正读取(仅作为增量的一部分),因此允许编译器将多个增量合并为一个。例如:
x := 0
for { x += 10 }
或者也许:
x := 0
for { x += 1<<64 }
与以下相同:
x := 0
for { x = 0 }
这显然是没有意义的,因此编译器会优化整个循环。