I tried to write sample program with 2 goroutines to illustrate race condition. One print x
, another increment x
.
package main
import (
"fmt"
"time"
)
func main() {
x := 0
go func() {
for {
if x%2 == 0 {
fmt.Println(x)
}
}
}()
go func() {
for {
x = x + 1
}
}()
<-time.After(1 * time.Second)
fmt.Println("final", x)
}
But incremental routine do nothing and finally in x
stays zero. But if incremental routine add fmt.Println(x)
it starts to increment x
and program working as expected.
UPDATE #1:
Purpose of this simple example is only to illustrate data race issue. I know that reading a shared variable between two goroutines without synchronization is a bad idea. Please stop to point me out to that. My question is why incremental goroutine do nothing here? In my picture of the world is should steadily increment, but the issue should appear in the reading goroutine where result may be not determined, because when reading x
is one, but when printing x
may be other.
UPDATE #2:
I have another example:
package main
import (
"time"
"fmt"
)
func main() {
x := 0
go func() {
last := x
for {
current := x
if last != current {
last = current
fmt.Println(last)
}
}
}()
go func() {
for {
//<-time.After(1 * time.Second) // if uncomment this row program start do "something"
x = x + 1
}
}()
<-time.After(10 * time.Second)
fmt.Println("final", x)
}
The example is pretty straightforward. One routine increment counter, other print if changed. This program has undefined behaviour only in the case when writing x = x+1
and read current := x
it is not atomic operation and one thread start read the variable in the middle of writing in the other thread and reading operations will read corrupted value. But why in this case when uncomment sleep in incremental routine program starts to do something?
You have data races: Go Data Race Detector. Therefore, your results are undefined.
package main
import (
"fmt"
"time"
)
func main() {
x := 0
go func() {
for {
if x%2 == 0 {
//fmt.Println(x)
}
}
}()
go func() {
for {
x = x + 1
}
}()
<-time.After(1 * time.Second)
fmt.Println("final", x)
}
Output:
$ go run -race racer.go > /dev/null
==================
WARNING: DATA RACE
Write at 0x00c000088010 by goroutine 7:
main.main.func2()
/home/peter/gopath/src/racer.go:21 +0x4e
Previous read at 0x00c000088010 by goroutine 6:
main.main.func1()
/home/peter/gopath/src/racer.go:13 +0x38
Goroutine 7 (running) created at:
main.main()
/home/peter/gopath/src/racer.go:19 +0x9c
Goroutine 6 (running) created at:
main.main()
/home/peter/gopath/src/racer.go:11 +0x7a
==================
Found 1 data race(s)
exit status 66
$
package main
import (
"fmt"
"time"
)
func main() {
x := 0
go func() {
for {
if x%2 == 0 {
fmt.Println(x)
}
}
}()
go func() {
for {
x = x + 1
}
}()
<-time.After(1 * time.Second)
fmt.Println("final", x)
}
Output:
$ go run -race racer.go > /dev/null
==================
WARNING: DATA RACE
Read at 0x00c00008c010 by goroutine 6:
main.main.func1()
/home/peter/gopath/src/racer.go:13 +0x3c
Previous write at 0x00c00008c010 by goroutine 7:
main.main.func2()
/home/peter/gopath/src/racer.go:21 +0x4e
Goroutine 6 (running) created at:
main.main()
/home/peter/gopath/src/racer.go:11 +0x7a
Goroutine 7 (running) created at:
main.main()
/home/peter/gopath/src/racer.go:19 +0x9c
==================
==================
WARNING: DATA RACE
Write at 0x00c00008c010 by goroutine 7:
main.main.func2()
/home/peter/gopath/src/racer.go:21 +0x4e
Previous read at 0x00c00008c010 by goroutine 6:
main.main.func1()
/home/peter/gopath/src/racer.go:13 +0x3c
Goroutine 7 (running) created at:
main.main()
/home/peter/gopath/src/racer.go:19 +0x9c
Goroutine 6 (running) created at:
main.main()
/home/peter/gopath/src/racer.go:11 +0x7a
==================
^Csignal: interrupt
$
You don't see anything happen because the compiler is smart and optimizes the increment away. Because there is no synchronization, the compiler is effectively presented with this code:
x := 0
for { x = x + 1 }
Since x
is never truly read anywhere (only as part of the increment) the compiler is allowed to coalesce multiple increments into one. For instance:
x := 0
for { x += 10 }
Or perhaps:
x := 0
for { x += 1<<64 }
Which is the same as:
x := 0
for { x = 0 }
This is obviously pointless and so the compiler optimizes the whole loop away.