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.
Regarding read
operation, why is it necessary to use atomic.LoadUnit
, rather than read this counter directly?
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)
Are we worrying about this scenario?
When CPU doing step3, another goroutine may read incomplete and dirty data from memory. So use atomic.LoadUint64
could avoid this kind of problem?
Are reads and writes for uint8 in golang atomic?
It's necessary to use atomic.LoadUint64
because there's no guarantee that the :=
operator does an atomic read.
For an example, consider a theoretical case where atomic.AddUint64
is implemented as follows:
If you do not use atomic.LoadUint64
, you could be reading an intermediary result between step 6 and 7.
On certain platforms (e.g. older ARM processors without native support for 64 bit integer operations), it may very well be implemented in the way described above.
The same applies for other sized integers/pointers as well. The exact behaviour will depend on the implementation of the atomic
package and the CPU/memory architecture that the program is running on.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With