完整代码在此处:https://play.golang.org/p/ggUoxtcv5m
go run -race main.go
说那里有竞争条件我无法解释。
不过,该程序输出了正确的最终结果。
本质:
type SafeCounter struct {
c int
sync.Mutex
}
func (c *SafeCounter) Add() {
c.Lock()
c.c++
c.Unlock()
}
var counter *SafeCounter = &SafeCounter{} // global
在递增器中使用*SafeCounter
:
func incrementor(s string) {
for i := 0; i < 20; i++ {
x := counter
x.Add()
counter = x
}
}
incrementor
方法在main
生成两次:
func main() {
go incrementor()
go incrementor()
// some other non-really-related stuff like
// using waitGroup is ommited here for problem showcase
}
所以,正如我所说的那样,go run -race main.go
总会说已经找到了一场比赛。
此外,最终结果始终是正确的(至少我已经多次运行此程序,它总是说最终计数器是40,这是正确的)。 但是,程序在开头打印不正确的值,因此您可以得到类似的内容:
Incrementor1: 0 Counter: 2
Incrementor2: 0 Counter: 3
Incrementor2: 1 Counter: 4
// ang the rest is ok
所以,那里缺少打印1
。
有人可以解释为什么我的代码存在竞争条件吗?
答案 0 :(得分:7)
你有许多种族条件,所有这些都是由比赛探测器特别指出的:
x := counter // this reads the counter value without a lock
fmt.Println(&x.c)
x.Add()
counter = x // this writes the counter value without a lock
time.Sleep(time.Duration(rand.Intn(3)) * time.Millisecond)
fmt.Println(s, i, "Counter:", x.c) // this reads the c field without a lock
比赛#1介于counter
incrementor
值的读取和写入之间
比赛#2介于counter
incrementor
值的并发写入之间
比赛#3介于x.c
中fmt.Println
字段的读取和x.c
方法中Add
的增量之间。
答案 1 :(得分:1)
读取和写入计数器指针的两行不受互斥锁的保护,并且是从多个goroutine同时完成的。
func incrementor(s string) {
for i := 0; i < 20; i++ {
x := counter // <-- this pointer read
x.Add()
counter = x // <-- races with this pointer write
}
}