原子操作可以完成地消除竟态条件,并能够绝对保证并发安全性。并且,它地执行速度要比其他的同步工具快得多,通常会高出好几个数量级。
学习golang笔记, 整理来自郝大的课程:Go语言核心36讲
原子操作(atomic operation)
原子操作可以完成地消除竟态条件,并能够绝对保证并发安全性。并且,它地执行速度要比其他的同步工具快得多,通常会高出好几个数量级。
不过,它的缺点也很明显。
更具体地说,正是因为原子操作不能被中断,所以它需要足够简单,并且要求快速
操作系统层面只针对二进制或整数的原子操作提供了支持。
sync/atomic包中提供了几种原子操作?可操作的数据类型又有哪些?
golang中原子操作如下:
加法(add)
比较并交换(CAS,compare and swap)
加载(load)
存储(store)
交换(swap)
数据类型有: int32、int64、uint32、uint64、uintptr以及unsafe包中的Pointer
unsafe.Pointer
1 | bytes := []byte{104, 101, 108, 108, 111} |
出于安全考虑,Go 语言并不支持直接操作内存,但它的标准库中又提供一种不安全(不保证向后兼容性) 的指针类型unsafe.Pointer
,让程序可以灵活的操作内存。
unsafe.Pointer
的特别之处在于,它可以绕过 Go 语言类型系统的检查,与任意的指针类型互相转换。也就是说,如果两种类型具有相同的内存结构(layout),我们可以将unsafe.Pointer
当做桥梁,让这两种类型的指针相互转换,从而实现同一份内存拥有两种不同的解读方式。
比如说,[]byte
和string
其实内部的存储结构都是一样的,但 Go 语言的类型系统禁止他俩互换。如果借助unsafe.Pointer
,我们就可以实现在零拷贝的情况下,将[]byte
数组直接转换成string
类型。
atomic.Value
atomic.Value
被设计用来存储任意类型的数据,所以它内部的字段是一个interface{}
类型,非常的简单粗暴。
1 | type Value struct { |
写入操作(Store)
1 | // Store sets the value of the Value to x. |
这个逻辑的主要思想就是,为了完成多个字段的原子性写入,我们可以抓住其中的一个字段,以它的状态来标志整个原子写入的状态。
流程图:
读取操作(Load)
1 | // Load returns the value set by the most recent Store. |
总结
原子操作由底层硬件支持,而锁则由操作系统提供的 API 实现。若实现相同的功能,前者通常会更有效率,并且更能利用计算机多核的优势。所以,以后当我们想并发安全的更新一些变量的时候,我们应该优先选择用atomic.Value来实现。
使用规则:
不能用atomic.Value原子值存储nil
我们向原子值存储的第一个值,决定了它今后能且只能存储哪一个类型的值
建议:不要把内部使用的atomic.Value原子值暴露给外界,如果非要暴露也要通过API封装形式,做严格的check。