这里有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
,它很容易理解。
关于read
操作,为什么有必要使用atomic.LoadUnit
而不是直接读取此计数器?
我可以用以下几行替换最后两行吗?
之前
opsFinal := atomic.LoadUint64(&ops) // Can I replace it?
fmt.Println("ops:", opsFinal)
之后
opsFinal := ops
fmt.Println("ops:", opsFinal)
我们担心这种情况吗?
CPU执行步骤3时,另一个goroutine可能会从内存中读取不完整且脏的数据。因此,使用atomic.LoadUint64
可以避免这种问题吗?
答案 0 :(得分:7)
必须使用atomic.LoadUint64
,因为不能保证:=
运算符会进行原子读取。
举个例子,考虑一个理论情况,其中atomic.AddUint64
的实现如下:
如果您不使用atomic.LoadUint64
,则可能是在第6步和第7步之间读取中间结果。
在某些平台上(例如,不支持64位整数运算的本地ARM处理器),很可能以上述方式实现。
其他大小的整数/指针也是如此。确切的行为取决于atomic
包的实现和程序所运行的CPU /内存体系结构。