Golang:原子读取有什么用?

时间:2019-04-25 00:55:01

标签: go

这里有Go by Example提供的go案例,用以解释原子包。

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)
}

对于atomic.AddUnit64,它很容易理解。

Question1

关于read操作,为什么有必要使用atomic.LoadUnit而不是直接读取此计数器?

问题2

我可以用以下几行替换最后两行吗?

之前

    opsFinal := atomic.LoadUint64(&ops) // Can I replace it?
    fmt.Println("ops:", opsFinal)

之后

    opsFinal := ops
    fmt.Println("ops:", opsFinal)

问题3

我们担心这种情况吗?

  1. CPU从内存中加载数据
  2. CPU处理数据
  3. 将数据写回内存。即使此步骤很快,但仍然需要时间。

CPU执行步骤3时,另一个goroutine可能会从内存中读取不完整且脏的数据。因此,使用atomic.LoadUint64可以避免这种问题吗?

参考

Are reads and writes for uint8 in golang atomic?

1 个答案:

答案 0 :(得分:7)

必须使用atomic.LoadUint64,因为不能保证:=运算符会进行原子读取。

举个例子,考虑一个理论情况,其中atomic.AddUint64的实现如下:

  1. 带上锁。
  2. 读取低32位。
  3. 读取高32位。
  4. 将数字添加到低32位。
  5. 将第一个操作的进位加到高32位。
  6. 写低32位。
  7. 写高32位。
  8. 释放锁。

如果您不使用atomic.LoadUint64,则可能是在第6步和第7步之间读取中间结果。

在某些平台上(例如,不支持64位整数运算的本地ARM处理器),很可能以上述方式实现。

其他大小的整数/指针也是如此。确切的行为取决于atomic包的实现和程序所运行的CPU /内存体系结构。