-race
标志会开启 Go 竞争监视器,它会使编译器创建一个典型的可执行文件的修改版。这个修改版可以记录所有对共享变量的访问以及发生的同步事件,包括调用 sync.Mutex
和 sync.WaitGroup
。分析相关事件后,竞争监视器打印一份报告来帮助您识别潜在问题,这样您就可以修正它们了。racec.go
。这个程序分三部分来介绍。raceC.go
的第一部分如下:raceC.go
的第二段代码如下:raceC.go
的其余代码如下:k
map 还不够,我们在调用 sync.Wait()
函数前再添加一个访问 k
map 的语句。raceC.go
,会获得如下没有任何警告或错误信息的输出:raceC.go
,当打印 k
map 内容的时候尽管您没有得到期望的,但一切看起来正常。然而,多次执行 raceC.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()
记号命名一个内部地匿名函数。如果您有不同地匿名函数,它们的名字同样会不同
raceC.go
的 main()
函数如下:aMutex
变量是一个定义在 main()
函数外的全局 sync.Mutex
变量,它可以在程序的任何地方访问到。尽管这不必要,不过有这样一个全局变量可以免得您总是把它传给函数。raceC.go
保存为 noRaceC.go
并执行它产生如下输出:noRaceC.go
产生如下输出:k
map 时,您需要一个锁机制。如果您没有使用这样的机制并且只修改了由 goroutine 执行的匿名函数的实现的话,您会从 go run noRaceC.go
获得如下输出:并发 map 写操作
。