即使在 golang 中使用 sync.Mutex 时的竞争条件

Race condition even when using sync.Mutex in golang

完整代码在这里: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

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

读取和写入计数器指针的两行不受互斥量保护,并且由多个goroutines并发完成。

func incrementor(s string) {
    for i := 0; i < 20; i++ {
        x := counter  // <-- this pointer read
        x.Add()
        counter = x   // <-- races with this pointer write
    }
}

您有许多竞态条件,均由竞态检测器专门指出:

    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
  • race #1 在 incrementor

  • 中的 counter 值的读取和写入之间
  • race #2 在对 incrementor

  • 中的 counter 值的并发写入之间
  • race #3 在 fmt.Println 中读取 x.c 字段和 Add 方法中增加到 x.c 之间。