Here we have a go case provided by Go by Example
, to explain the atomic package.
https://gobyexample.com/atomic-counters
package main
import "fmt"
import "time"
import "sync/atomic"
func main() {
var ops uint64
for i := 0; i < 50; i++ {
go func() {
for {
atomic.AddUint64(&ops, 1)
time.Sleep(time.Millisecond)
}
}()
}
time.Sleep(time.Second)
opsFinal := atomic.LoadUint64(&ops) // Can I replace it?
fmt.Println("ops:", opsFinal)
}
For atomic.AddUnit64
, it's straightforward to understand.
Question1
Regarding read
operation, why is it necessary to use atomic.LoadUnit
, rather than read this counter directly?
Question2
Can I replace the last two lines with the following lines?
Before
opsFinal := atomic.LoadUint64(&ops) // Can I replace it?
fmt.Println("ops:", opsFinal)
After
opsFinal := ops
fmt.Println("ops:", opsFinal)
Question3
Are we worrying about this scenario?
- CPU loads the data from memory
- CPU manipulates data
- Write data back to memory. Even though this step is fast, but it still takes time.
When CPU doing step3, another goroutine may read incomplete and dirty data from memory. So use atomic.LoadUint64
could avoid this kind of problem?