Issue
Is it concurrent safe to Stop a gouroutine after a period of time, like this?
- Code: (Note: data race since
ok
changes in another goroutine):
package main
import (
"fmt"
"time"
)
func main() {
var ok byte
time.AfterFunc(1000*time.Millisecond, func() {
ok = 1
})
var i uint64
for ok == 0 {
i++ // CPU intensive task
}
fmt.Println(i) // 2_776_813_033
}
Terminal:
go run -race .
==================
WARNING: DATA RACE
Write at 0x00c000132010 by goroutine 8:
main.main.func1()
./main.go:11 +0x46
Previous read at 0x00c000132010 by main goroutine:
main.main()
./main.go:15 +0xf4
Goroutine 8 (running) created at:
time.goFunc()
go/src/time/sleep.go:180 +0x51
==================
80849692
Found 1 data race(s)
- Code (No data race):
package main
import (
"fmt"
"sync/atomic"
"time"
)
func main() {
var ok int32
time.AfterFunc(1000*time.Millisecond, func() {
atomic.StoreInt32(&ok, 1)
})
var i uint64
for atomic.LoadInt32(&ok) == 0 {
i++ // CPU intensive task
}
fmt.Println(i) // 2_835_935_488
}
Terminal:
go run -race .
31934042
Solution
There is no guarantee that the busy-wait for loop will terminate even if ok
is set to false
by another goroutine. There is no explicit synchronization during the setting and reading of ok
, so the main goroutine is not guaranteed to see the changes made to it. In other words, there is no way to establish a happened-before relationship between the two goroutines.
Second version of the code is safe even though it is not stated in the Go memory model with respect to ok
, but it is not safe because such tight loops may not allow other goroutines to execute. Atomic read/write has the memory barriers necessary for a happened-before relationship. You should use one of the synchronization primitives (mutex, channel) to guarantee that.
Answered By – Burak Serdar
Answer Checked By – Terry (GoLangFix Volunteer)