Why does the code snippet below print out 0
? However, i
is correctly updated to 1
without the for
loop.
package main
import (
"fmt"
"time"
)
func main() {
var i uint64
go func() {
for {
i++
}
}()
time.Sleep(time.Second)
fmt.Println(i)
}
Solution 1
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
runtime.GOMAXPROCS(1)
var i uint64
ch := make(chan bool)
go func() {
for {
select {
case <-ch:
return
default:
i++
}
}
}()
time.Sleep(time.Second)
ch <- true
fmt.Println(i)
}
Solution 2
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func main() {
runtime.GOMAXPROCS(1)
var i uint64
var mx sync.Mutex
go func() {
for {
mx.Lock()
i++
mx.Unlock()
}
}()
time.Sleep(time.Second)
mx.Lock()
fmt.Println(i)
mx.Unlock()
}
Solution 3
package main
import (
"fmt"
"runtime"
"sync/atomic"
"time"
)
func main() {
runtime.GOMAXPROCS(1)
var i uint64
go func() {
for {
atomic.AddUint64(&i, 1)
runtime.Gosched()
}
}()
time.Sleep(time.Second)
fmt.Println(atomic.LoadUint64(&i))
}
You have a race condition. The results are undefined.
package main
import (
"fmt"
"time"
)
func main() {
var i uint64
go func() {
for {
i++
}
}()
time.Sleep(time.Second)
fmt.Println(i)
}
Output:
$ go run -race racer.go
==================
WARNING: DATA RACE
Read at 0x00c0000a2010 by main goroutine:
main.main()
/home/peter/src/racer.go:18 +0x95
Previous write at 0x00c0000a2010 by goroutine 6:
main.main.func1()
/home/peter/src/racer.go:13 +0x4e
Goroutine 6 (running) created at:
main.main()
/home/peter/src/racer.go:11 +0x7a
==================
66259553
Found 1 data race(s)
exit status 66
$
Reference: Introducing the Go Race Detector
It's not "correctly" updated without the for loop, it's just randomly updated before you print it.
In go playgorund this ends with:
process took too long
Program exited.
See: https://play.golang.org/p/qKd-CdLt3uK
When starting the go routine the scheduler will decide when to switch the context between the execution of the go routine and the main function. In addition the go-routine is stopped when the main function ends.
What you want to do is some Synchronisation between your go routine and your main loop, since both are accessing a shared variable i
.
Best practice here would be to use channels, sync.Mutex or sync.WaitGroup.
You can add some delay to your for loop, to give up some CPU for the main function:
go func() {
for {
i++
time.Sleep(1 * time.Millisecond)
}
}()
This will pint some value around 1000, but it's still not reliable.
Running this in the go playground, it times out, which slightly surprises me because, as Tarion says, the infinite for loop should be terminated upon exit from main
. It seems that the go playground waits for all goroutines to complete before exiting. I imagine the reason you see 0 is to do with bad multithreading here. The for loop is continually updating i
at the same time as i
is read by main, and the results are unpredictable. I'm not sure what you are trying to demonstrate, but you would need a mutex here or a channel to read and update i
safely on different threads.
Necessary reading for understanding - The Go Memory Model
The Go memory model specifies the conditions under which reads of a variable in one goroutine can be guaranteed to observe values produced by writes to the same variable in a different goroutine.
Whenever you have 2 or more concurrent goroutines accessing same memory and one of them modifying memory (like i++
in your example) you need to have some sort of synchronization: mutex or channel.
Something like this would be smallest modification necessary to your code:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var (
mu sync.Mutex
i uint64
)
go func() {
for {
mu.Lock()
i++
mu.Unlock()
}
}()
time.Sleep(time.Second)
mu.Lock()
fmt.Println(i)
mu.Unlock()
}
Oh and official Advice:
If you must read the rest of this document to understand the behavior of your program, you are being too clever. Don't be clever.
:) have fun