Reputation: 9649
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))
}
Upvotes: 1
Views: 2520
Reputation: 1313
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
Upvotes: 2
Reputation: 166578
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
Upvotes: 2
Reputation: 12035
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.
Upvotes: 1
Reputation: 17134
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.
Upvotes: 0