即使在golang中使用sync.Mutex也会出现竞争情况

时间:2017-05-03 23:46:38

标签: go mutex race-condition

完整代码在此处: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

有人可以解释为什么我的代码存在竞争条件吗?

2 个答案:

答案 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.cfmt.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
    }
}