atomic概述
atomic是Go内置原子操作包。下面是官方说明:
Package atomic provides low-level atomic memory primitives useful for implementing synchronization algorithms. atomic包提供了用于实现同步机制的底层原子内存原语。
These functions require great care to be used correctly. Except for special, low-level applications, synchronization is better done with channels or the facilities of the sync package. Share memory by communicating; don’t communicate by sharing memory. 使用这些功能需要非常小心。除了特殊的底层应用程序外,最好使用通道或sync包来进行同步。通过通信来共享内存;不要通过共享内存来通信。
atomic包提供的操作可以分为三类:
对整数类型T的操作
T类型是int32、int64、uint32、uint64、uintptr其中一种。
1 | func AddT(addr *T, delta T) (new T) |
对于unsafe.Pointer类型的操作
1 | func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool) |
atomic.Value类型提供Load/Store操作
atomic提供了atomic.Value类型,用来原子性加载和存储类型一致的值(consistently typed value)。atomic.Value提供了对任何类型的原则性操作。
1 | func (v *Value) Load() (x interface{}) // 原子性返回刚刚存储的值,若没有值返回nil |
用法
用法示例1:原子性增加值
1 | package main |
用法示例2:简易自旋锁实现
1 | package main |
用法示例3: 无符号整数减法操作
对于Uint32和Uint64类型Add方法第二个参数只能接受相应的无符号整数,atomic包没有提供减法SubstractT操作:
1 | func AddUint32(addr *uint32, delta uint32) (new uint32) |
对于无符号整数V,我们可以传递-V给AddT方法第二个参数就可以实现减法操作。
1 | package main |
源码分析
atomic包提供的三类操作的前两种都是直接通过汇编源码实现的(sync/atomic/asm.s):
1 | #include "textflag.h" |
从上面汇编代码可以看出来atomic操作通过JMP操作跳到runtime/internal/atomic目录下面的汇编实现。我们把目标转移到runtime/internal/atomic目录下面。
该目录包含针对不同平台的atomic汇编实现asm_xxx.s。这里面我们只关注amd64平台asm_amd64.s(runtime/internal/atomic/asm_amd64.s)和atomic_amd64.go(runtime/internal/atomic/atomic_amd64.go)。
| 函数 | 底层实现 |
|---|---|
| SwapInt32 / SwapUint32 | runtime∕internal∕atomic·Xchg |
| SwapInt64 / SwapUint64 / SwapUintptr | runtime∕internal∕atomic·Xchg64 |
| CompareAndSwapInt32 / CompareAndSwapUint32 | runtime∕internal∕atomic·Cas |
| CompareAndSwapUintptr / CompareAndSwapInt64 / CompareAndSwapUint64 | runtime∕internal∕atomic·Cas64 |
| AddInt32 / AddUint32 | runtime∕internal∕atomic·Xadd |
| AddUintptr / AddInt64 / AddUint64 | runtime∕internal∕atomic·Xadd64 |
| LoadInt32 / LoadUint32 | runtime∕internal∕atomic·Load |
| LoadInt64 / LoadUint64 / LoadUint64/ LoadUintptr | runtime∕internal∕atomic·Load64 |
| LoadPointer | runtime∕internal∕atomic·Loadp |
| StoreInt32 / StoreUint32 | runtime∕internal∕atomic·Store |
| StoreInt64 / StoreUint64 / StoreUintptr | runtime∕internal∕atomic·Store64 |
Add操作
AddUintptr 、 AddInt64 以及 AddUint64都是由方法runtime∕internal∕atomic·Xadd64实现:
1 | TEXT runtime∕internal∕atomic·Xadd64(SB), NOSPLIT, $0-24 |
LOCK指令是一个指令前缀,其后是读-写性质的指令,在多处理器环境中,LOCK指令能够确保在执行LOCK随后的指令时,处理器拥有对数据的独占使用。若对应数据已经在cache line里,也就不用锁定总线,仅锁住缓存行即可,否则需要锁住总线来保证独占性。
XADDQ指令用于交换加操作,会将源操作数与目的操作数互换,并将两者的和保存到源操作数中。
AddInt32 、 AddUint32 都是由方法runtime∕internal∕atomic·Xadd实现,实现逻辑和runtime∕internal∕atomic·Xadd64一样,只是Xadd中相关数据操作指令后缀是L:
1 | TEXT runtime∕internal∕atomic·Xadd(SB), NOSPLIT, $0-20 |
Store操作
StoreInt64、StoreUint64、StoreUintptr三个是runtime∕internal∕atomic·Store64方法实现:
1 | TEXT runtime∕internal∕atomic·Store64(SB), NOSPLIT, $0-16 |
XCHGQ指令是交换指令,用于交换源操作数和目的操作数。
StoreInt32、StoreUint32是由runtime∕internal∕atomic·Store方法实现,与runtime∕internal∕atomic·Store64逻辑一样,这里不在赘述。
CompareAndSwap操作
CompareAndSwapUintptr、CompareAndSwapInt64和CompareAndSwapUint64都是由runtime∕internal∕atomic·Cas64实现:
1 | TEXT runtime∕internal∕atomic·Cas64(SB), NOSPLIT, $0-25 |
CMPXCHGQ指令是比较并交换指令,它的用法是将目的操作数和累加寄存器AX进行比较,若相等,则将源操作数复制到目的操作数中,否则将目的操作复制到累加寄存器中。
Swap操作
SwapInt64、SwapUint64、SwapUintptr实现的方法是runtime∕internal∕atomic·Xchg64,SwapInt32和SwapUint32底层实现是runtime∕internal∕atomic·Xchg,这里面只分析64的操作:
1 | TEXT runtime∕internal∕atomic·Xchg64(SB), NOSPLIT, $0-24 |
Load操作
LoadInt32、LoadUint32、LoadInt64 、 LoadUint64 、 LoadUint64、 LoadUintptr、LoadPointer实现都是Go实现的:
1 | //go:linkname Load |
最后我们来分析atomic.Value类型提供Load/Store操作。
atomic.Value类型的Load/Store操作
atomic.Value类型定义如下:
1 | type Value struct { |
atomic.Value底层存储的是空接口类型,空接口底层结构如下:
1 | type eface struct { |
atomic.Value内存布局如下所示:

从上图可以看出来atomic.Value内部分为两部分,第一个部分是_type类型指针,第二个部分是unsafe.Pointer类型,两个部分大小都是8字节(64系统下)。我们可以通过以下代码进行测试:
1 | type Value struct { |
接下来我们看下Store方法:
1 | func (v *Value) Store(x interface{}) { |
总结上面Store流程:
- 每次调用Store方法时候,会将传入参数转换成interface{}类型。当第一次调用Store方法时候,分两部分操作,分别将传入参数空接口类型的_typ和data,存储到Value类型中。
- 当再次调用Store类型时候,进行传入参数空接口类型的_type和Value的_type比较,若不一致直接panic,若一致则将data存储到Value类型中
从流程2可以看出来,每次调用Store方法时传入参数都必须是同一类型的变量。当Store完成之后,实现了“鸠占鹊巢”,atomic.Value底层存储的实际上是(interface{})x。
最后我们看看atomic.Value的Load操作:
1 | func (v *Value) Load() (x interface{}) { |