当运行或构建 Go 源文件时使用 -race 标志会开启 Go 竞争监视器,它会使编译器创建一个典型的可执行文件的修改版。这个修改版可以记录所有对共享变量的访问以及发生的同步事件,包括调用 sync.Mutex 和 sync.WaitGroup。分析相关事件后,竞争监视器打印一份报告来帮助您识别潜在问题,这样您就可以修正它们了。
请看下面的 Go 代码,它保存为 racec.go。这个程序分三部分来介绍。
raceC.go 的第一部分如下:
packagemainimport("fmt""os""strconv""sync")funcmain(){arguments:=os.Argsiflen(arguments)!=2{fmt.Println("Give me a natural number!")os.Exit(1)}numberGR,err:=strconv.Atoi(os.Args[1])iferr!=nil{fmt.Println(err)return}
raceC.go 的第二段代码如下:
raceC.go 的其余代码如下:
好像许多 goroutines 同时访问 k map 还不够,我们在调用 sync.Wait() 函数前再添加一个访问 k map 的语句。
如果您执行 raceC.go,会获得如下没有任何警告或错误信息的输出:
如果您仅执行一次 raceC.go,当打印 k map 内容的时候尽管您没有得到期望的,但一切看起来正常。然而,多次执行 raceC.go 告诉我们这有些错误,主要是每次执行产生不同的输出。
如果我们决定使用 Go 竞争监视器分析 raceC.go,我们可以获得更多信息及意外输出:
""
竞争监视器发现两处数据竞争。每个都在它的输出里用 WARNING: DATA RACE 消息开头。
第一个 数据竞争 发生在 main.main.func1() 内,它由一个goroutine 执行的 for 循环调用。这的问题由 Previous write 消息表示。检查相关代码后,很容易看到实际问题是匿名函数没带参数,意思是在 for 循环中使用的 i 值不同准确识别,因为这是个写操作,在 for 循环内不断改变。
第二处数据竞争信息是 Write at 0x00c420074180 by goroutine 7。如果您阅读相关输出,会看到这个数据竞争是关于写操作的,并且至少有两个 goroutines 在执行。因为这两个 goroutine 有相同的名字(main.main.func1()),这表明我们谈论的是同一个 goroutine。这两个 goroutine 试图写同一个变量,这就是数据竞争状态!
Go 用 main.main.func1() 记号命名一个内部地匿名函数。如果您有不同地匿名函数,它们的名字同样会不同
var waitGroup sync.WaitGroup
var i int
k := make(map[int]int)
k[1] = 12
for i = 0; i < numGR; i++ {
waitGroup.Add(1)
go func() {
defer waitGroup.Done()
k[i] = i
}()
}