开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 23 天,点击查看活动详情
atomic
在 Go 语言中,atomic 包提供了一系列原子操作函数,用于对内存的读取、修改操作进行原子性的保障,从而实现并发安全。
原子操作:原子操作指的是在执行过程中不能被其他操作中断的操作。在执行的时候,其它线程不会看到执行一半的操作结果。在其它线程看来,原子操作要么执行完了,要么还没有执行,就像一个最小的粒子原子一样,不可分割。原子操作基于底层硬件提供的原子指令实现,这些指令保证了在执行原子操作期间,不会发生中断或者并发访问,因此能够保证操作的原子性。
atomic 提供的方法
Addxxx
AddInt32、AddInt64、AddUint32、AddUint64、AddUintptr
可以实现原子加减操作
a := int64(5)
atomic.AddInt64(&a, 5)
Storexxx、Loadxxx
StoreInt32、StoreInt64、StoreUint32、StoreUint64、StoreUintptr、StorePointer LoadInt32、LoadInt64、LoadUint32、LoadUint64、LoadUintptr、LoadPointer 原子写入与读取。当地址没有对齐,处理器就需要分成两个指令去处理,如果执行了一个指令,其它人就会看到更新了一半的错误的数据,这被称做撕裂写(torn write)。
多处理器多核心系统为了处理这类问题,使用了一种叫做内存屏障(memory fence 或memory barrier)的方式。一个写内存屏障会告诉处理器,必须要等到它管道中的未完成的操作(特别是写操作)都被刷新到内存中,再进行操作。此操作还会让相关的处理器的CPU 缓存失效,以便让它们从主存中拉取最新的值。
atomic 包提供的方法会提供内存屏障的功能,所以,atomic 不仅仅可以保证赋值的数据完整性,还能保证数据的可见性,一旦一个核更新了该地址的值,其它处理器总是能读取到它的最新值。但是,需要注意的是,因为需要处理器之间保证数据的一致性,atomic 的操作也是会降低性能的。
CAS (CompareAndSwap)
CompareAndSwapInt32、CompareAndSwapInt64、CompareAndSwapUint32、CompareAndSwapUint64、CompareAndSwapUintptr、CompareAndSwapPointer
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
功能就是原子操作:
- 当addr==old时,addr=new,返回true
- 当addr!=old时,返回false
可以把这个当作乐观锁,先load,然后修改,最后保存的时候用CAS,如果有人已经改了,就重试:
a := int64(5)
for {
old := atomic.LoadInt64(&a)
new := old + 5
if atomic.CompareAndSwapInt64(&a, old, new) {
break
}
}
fmt.Println(a)
Swap
SwapInt32、SwapInt64、SwapUint32、SwapUint64、SwapUintptr、SwapPointer 直接原子交换。
Value
atomic 还提供了一个特殊的类型:Value。它可以原子地存取对象类型, CAS 和 Swap。我们就可以利用Value存储结构体,并对其进行原子操作了。
type Config struct {
NodeName string
Addr string
Count int32
}
func loadNewConfig() Config {
return Config{NodeName: "北京", Addr: "10.77.95.27", Count: rand.Int31()}
}
func main() {
var config atomic.Value
config.Store(loadNewConfig())
var cond = sync.NewCond(&sync.Mutex{}) // 设置新的config
go func() {
for {
time.Sleep(time.Duration(5+rand.Int63n(5)) * time.Second)
config.Store(loadNewConfig())
cond.Broadcast() // 通知等待着配置已变更
}
}()
go func() {
for {
cond.L.Lock()
cond.Wait() // 等待变更信号
c := config.Load().(Config) // 读取新的配置
fmt.Printf("new config: %+v\n", c)
cond.L.Unlock()
}
}()
select {}
}