为什么 goroutine 的竞争条件不会在某个时候发生?

Why race condition with goroutine won't happen some time?

我正在阅读实战。这个例子来自 chapter6/listing09.go.

// This sample program demonstrates how to create race
// conditions in our programs. We don't want to do this.
package main

import (
  "fmt"
  "runtime"
  "sync"
)

var (
  // counter is a variable incremented by all goroutines.
  counter int

  // wg is used to wait for the program to finish.
  wg sync.WaitGroup
)

// main is the entry point for all Go programs.
func main() {
  // Add a count of two, one for each goroutine.
  wg.Add(2)

  // Create two goroutines.
  go incCounter(1)
  go incCounter(2)

  // Wait for the goroutines to finish.
  wg.Wait()
  fmt.Println("Final Counter:", counter)
 }

 // incCounter increments the package level counter variable.
 func incCounter(id int) {
    // Schedule the call to Done to tell main we are done.
    defer wg.Done()

    for count := 0; count < 2; count++ {
      // Capture the value of Counter.
      value := counter

      // Yield the thread and be placed back in queue.
      runtime.Gosched()

      // Increment our local value of Counter.
      value++

      // Store the value back into Counter.
      counter = value
  }
}

如果你运行这个代码在play.golang.org,就是2,和书上的一样。 但是我的 mac 大部分时间打印 4,有时打印 2,有时甚至打印 3。

$ go run listing09.go 
Final Counter: 2
$ go run listing09.go 
Final Counter: 4
$ go run listing09.go 
Final Counter: 4
$ go run listing09.go 
Final Counter: 4
$ go run listing09.go 
Final Counter: 4
$ go run listing09.go 
Final Counter: 2
$ go run listing09.go 
Final Counter: 4
$ go run listing09.go 
Final Counter: 2
$ go run listing09.go 
Final Counter: 3

系统信息 go 版本 go1.8.1 darwin/amd64 macOS 塞拉利昂 Macbook Pro

书中的解释(p140)

Each goroutine overwrites the work of the other. This happens when the goroutine swap is taking place. Each goroutine makes its own copy of the counter variable and then is swapped out for the other goroutine. When the goroutine is given time to exe- cute again, the value of the counter variable has changed, but the goroutine doesn’t update its copy. Instead it continues to increment the copy it has and set the value back to the counter variable, replacing the work the other goroutine performed.

根据这个解释,这段代码应该总是打印 2。

  1. 为什么我得到了 4 和 3?是因为竞争条件没有发生吗?

  2. 为什么去游乐场总是2个?


更新:

我设置runtime.GOMAXPROCS(1)后,开始打印2,no 4,some 3。 我猜 play.golang.org 配置为具有一个逻辑处理器。

没有竞争条件的正确结果 4。一个逻辑处理器意味着一个线程。 GO 具有与物理内核相同的逻辑处理器default.So, 为什么一个线程(一个逻辑处理器)导致竞争条件而多个线程打印正确答案?

我们也得到了3和4,能说书上的解释是错误的吗? 它如何得到 3 ? 4 是正确的。

根据定义,竞争条件是不确定的。这意味着虽然大多数时候您可能会得到一个特定的答案,但并非总是如此。

通过 运行 多核上的活泼代码,您可以大大增加可能性的数量,从而获得更广泛的结果选择。

有关竞争条件的更多信息,请参阅 this post or this Wikipedia article