2

I'm reading go-in-action. This example is from 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
  }
}

If you run this code in play.golang.org, it will be 2, same as the book. But my mac print 4 most of the time, some time 2, some time even 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

sysinfo go version go1.8.1 darwin/amd64 macOS sierra Macbook Pro

Explanation from the book(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.

According to this explanation, this code should always print 2.

  1. Why I got 4 and 3? Is it because race condition didn't happen?

  2. Why go playground always get 2?


update:

After I set runtime.GOMAXPROCS(1), it starts to print 2, no 4, some 3. I guess the play.golang.org is configured to have one logical processor.

The right result 4 without race condition. One logical processor means one thread. GO has same logical processors as the physical cores by default.So, why one thread(one logical processor) leads to race condition while multiple thread print the right answer?

Can we say the explanation from the book is wrong since we also get 3 and 4 ? How it get 3 ? 4 is correct.

  • 1
    Milo's answer is great, but Go can also tell you about this race condition even in cases where it doesn't bite you (the "race" always happens; it's just sometimes you win, sometimes you lose different ways). Try running this as `go run --race test.go` and you'll see the exact line of code that is impacted by the race. – Rob Napier Jul 23 '17 at 16:01
  • @RobNapier thanks a lot –  Jul 24 '17 at 02:36

1 Answers1

1

Race conditions are, by definition, nondeterministic. This means that while you may get a particular answer most of the time, it will not always be so.

By running racy code on multiple cores you greatly increases the number of possibilities, hence you get a broader selection of results.

See this post or this Wikipedia article for more information on race conditions.

Milo Christiansen
  • 3,204
  • 2
  • 23
  • 36
  • Single thread can only run on one core, right? With multiple thread it print 4(the correct result) more often. Why is this ? I have an update on my question. Thanks ! –  Jul 24 '17 at 02:45
  • This is one of the "joys" of race conditions. There is no real way to tell what will happen or why. Why does it produce "correct" results more often on more cores? It probably has something to do with the exact relative speeds of various functions, and little details of the scheduler implementation. If you change the code or number and speed of cores the results will probably change randomly. – Milo Christiansen Jul 24 '17 at 03:25
  • Hi, if I comment runtime.GoSched(), it will always be 4, no race condition, right? –  Jul 24 '17 at 23:05
  • Nope. The race will still be there, it just will be less likely to strike. To prevent data races you need explicit synchronization like mutexes or channels. – Milo Christiansen Jul 24 '17 at 23:26