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.
Why I got 4 and 3? Is it because race condition didn't happen?
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.