golang的atomic.LoadInt32/StoreInt32函数的用法
func LoadInt32(addr *int32) (val int32)
func StoreInt32(addr *int32, val int32)
直观上理解是把一个32位整数读出或者写入内存,保证读写的原子性。
为什么会有这样的需求?难道读写一个整数不是CPU指令级别能保证的原子操作吗?
我的理解,主要是基于两个原因:
兼容不同的计算机体系结构,从语言层面提供一个统一的函数。
例如在现在32位/64位 RISC计算机体系结构下面,一条load/store指令就能读取32位/64位的数据,那么CPU指令级别确实能保证int32的读取完整性,也就LoadInt32/StoreInt32失去存在的必要性。
但是不同的计算机体系结构,不一定能提供这种指令,举个例子部分老式CPU可能需要4条指令,一次读取一个字节,这种情况下就必须保证读写的原子性。
还有啊在32位计算机体系结构下,读写一个64位的数据,必然需要两条指令,这时LoadInt64/StoreInt64就需要了。提供读写的同步
直接变量访问指令可能不会引起内存的读取,现在编译器都会做优化,可以把变量优化到寄存器,在代码运气期间对变量的读写直接访问寄存器,而不写入内存;这在同一个执行上下文是没有问题的,而如果需要跨线程访问,则永远看不到变量值的变化。下面一个例子说明这个问题:
package main
import (
"fmt"
"time"
)
var x int64 = 0x3333333333333333
func storeFunc() {
for i := 0; ;i++ {
if i % 2 == 0 {
x = 0x1111111111111111
} else {
x = 0x2222222222222222
}
//time.Sleep(10 * time.Millisecond)
}
}
func main() {
go storeFunc()
for {
time.Sleep(10 * time.Millisecond)
fmt.Printf("%x\n", x)
}
}
这段代码:
- 定义一个全局变量x初始值为0x333333333333
- 定义一个goroutine,依次修改变量x的值为0x1111111111111111和0x2222222222222222
- 在main里面,不停的读取x的值,打印出来。
- 我们会看到打印出来的结果始终是:0x3333333333333333
也就是说storeFunc的代码没有被执行到;实际上是函数storeFunc的代码编译器做了优化,对x的赋值并没有写回到内容(可能就存储在寄存器中),而storeFunc和main是在两个goroutine里面运行的,他们并不共享CPU执行上下文,从而main读出的值永远是初始值。
如果我们在storeFunc的循环内加一个sleep间隔,让x的值能够被写入内容,那么main函数就会读出0x1111111111111111和0x2222222222222222的值。
有疑问加站长微信联系(非本文作者)