Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang: what is atomic read used for?

Tags:

go

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?

  1. CPU loads the data from memory
  2. CPU manipulates data
  3. 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?

Reference

Are reads and writes for uint8 in golang atomic?

like image 704
Ryan Lyu Avatar asked Apr 25 '19 00:04

Ryan Lyu


1 Answers

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:

  1. Take a lock.
  2. Read the lower 32 bits.
  3. Read the upper 32 bits.
  4. Add the number to the lower 32 bits.
  5. Add carry-out of the first operation to the upper 32 bits.
  6. Write the lower 32 bits.
  7. Write the upper 32 bits.
  8. Release lock.

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.

like image 167
tangrs Avatar answered Sep 19 '22 21:09

tangrs