Content #
因为不同的 CPU 架构甚至不同的版本提供的原子操作的指令是不同的,所以,要用一种编程语言实现支持不同架构的原子操作是相当有难度的。不过,还好这些都不需要你操心,因为 Go 提供了一个通用的原子操作的 API,将更底层的不同的架构下的实现封装成 atomic 包,提供了修改类型的原子操作(atomic read-modify-write,RMW)和加载存储类型的原子操作(Load 和 Store)的 API。
有的代码也会因为架构的不同而不同。有时看起来貌似一个操作是原子操作,但实际上,对于不同的架构来说,情况是不一样的。比如下面的代码的第 4 行,是将一个 64 位的值赋值给变量 i:
const x int64 = 1 + 1<<33
func main() {
var i = x
_ = i
}
如果你使用 GOARCH=386 的架构去编译这段代码,那么,第 5 行其实是被拆成了两个指令,分别操作低 32 位和高 32 位。反编译指令:
> GOARCH=386 go tool compile -N -l test.go
> GOARCH=386 go tool objdump -gnu test.o
main.go:5 0x37b 83ec08 SUBL $0x8, SP // sub $0x8,%esp
main.go:6 0x37e c7042401000000 MOVL $0x1, 0(SP) // movl $0x1,(%esp)
main.go:6 0x385 c744240402000000 MOVL $0x2, 0x4(SP) // movl $0x2,0x4(%esp)
main.go:8 0x38d 83c408 ADDL $0x8, SP // add $0x8,%esp
如果 GOARCH=amd64 的架构去编译这段代码,那么,第 5 行其中的赋值操作其实是一条指令:
❯ GOARCH=amd64 go tool compile -N -l main.go
❯ GOARCH=amd64 go tool objdump -gnu main.o
main.go:5 0x2fd 4883ec10 SUBQ $0x10, SP // sub $0x10,%rsp
main.go:5 0x301 48896c2408 MOVQ BP, 0x8(SP) // mov %rbp,0x8(%rsp)
main.go:5 0x306 488d6c2408 LEAQ 0x8(SP), BP // lea 0x8(%rsp),%rbp
main.go:6 0x30b 48b80100000002000000 MOVQ $0x200000001, AX // mov $0x200000001,%rax
main.go:6 0x315 48890424 MOVQ AX, 0(SP) // mov %rax,(%rsp)
main.go:8 0x319 488b6c2408 MOVQ 0x8(SP), BP // mov 0x8(%rsp),%rbp
main.go:8 0x31e 4883c410 ADDQ $0x10, SP // add $0x10,%rsp
main.go:8 0x322 c3 RET // retq
所以,如果要想保证原子操作,切记一定要使用 atomic 提供的方法。
Viewpoints #
From #
12 | atomic:要保证原子操作,一定要使用这几种方法