atomic包
atmoic包可以对int32、int64、uint32、uint64、uintptr、unsafe.Pointer提供五种原子性操作分别是增或减、比较并交换、载入、存储和交换。
原子增或减
简介
atomic包中有类似AddInt32等这样的方法,它需要两个参数,第一个是需要修改的int32类型的变量地址,第二个是add到该变量上且与改变量类型相同的值。
例子
我们的例子是用10个线程每个线程都将变量value(初始值为0)增加1000次,那么最终结果是10000。不用原子操作的话我们可以用channel或mutex来实现来保证对value++的操作的原子性。atomic包提供的AddInt32等此类的方法同样可以实现value++操作的原子性并且效率更高。
func main() {
var count int32
waitgroup:=sync.WaitGroup{}
waitgroup.Add(10)
for i:=0;i<10;i++{
go func(){
for i:=0;i<1000;i++{
atomic.AddInt32(&count,1)
}
waitgroup.Done()
}()
}
waitgroup.Wait()
fmt.Println(count)
}
输出的结果是10000与预期相符合。
比较并交换(CAS)
简介
以CompareAndSwapInt32为例,函数声明为
func CompareAndSwapInt32(addr *int32,old,new int32)(swapped bool)
该方法会比较需要修改的目标变量地址(addr)中的值与读取出来的目标变量的值(old)是否一致,一致的话将目标变量地址指向的值更新为new否则更新操作就会被忽略。
CAS与锁相比,锁的做法是悲观的因为锁总是认为在操作目标变量时总是有其他线程对该目标变量进行修改。而CAS更趋于乐观,它总是假定没有其他线程对当前目标变量进行修改。
该方法适用于被操作的值不会被其他线程频繁更新的情景,如果被操作的值被其他线程频繁更新则不建议使用CAS。
例子
我们使用CompareAndSwap方法代替上面的AddInt方法,其中for循环是循环等待直到更新成功才会推出for循环。
func main() {
var count int32
waitgroup:=sync.WaitGroup{}
waitgroup.Add(10)
for i:=0;i<10;i++{
go func(){
for i:=0;i<1000;i++{
swapped:=true
for swapped{
swapped=!atomic.CompareAndSwapInt32(&count,count,count+1)
}
}
waitgroup.Done()
}()
}
waitgroup.Wait()
fmt.Println(count)
}
执行结果是10000符合预期结果。
载入
原子性的载入可以保证你在读取目标变量时没有其他线程对该变量进行读写。
其使用方法为
var value=int32(1)
result:=atomic.LoadInt32(&value)
存储
简介
在原子的存储某个值过程中,任何cpu都不会进行针对该值的读写操作。原子存储并不关心被操作的旧值是什么,它只是单纯的将旧值替换成新值。
以int32为例原子存储的方法声明如下
func StoreInt32(addr *int32, new int32)
交换
简介
在原子的交换某个值过程中,任何cpu都不会进行针对该值的读写操作。原子交接并不会关心被操作的旧值,它比原子存储操作多一个步骤就是返回旧值。
函数声明如下:
func SwapInt32(addr *int32, new int32) (old int32)
原子值
atomic.Value是一个结构体类型称为"原子值类型",它用于存储需要原子读写的值。其接受的被操作的值类型不限。
声明一个原子值如下:
var atomicVal atomic.Value
该类型有两个公开的指针方法Load和Stroe。Load可以原子地读取原子值中存储的值,返回一个interface{]类型的结果。Store用于原子地在原子值实例中存储一个值,它接受一个个interface{}类型的参数而没有任何结果。在未曾通过store方法向原子值实例存储值之前,它的load方法总会返回nil。
原子值实例的Store方法传入的参数不能为nil,并且每次传入的参数的类型必须一致。
注意如果atomic.Value类型的值在传入其他函数时,一定要传 *atmoic.value类型,否则传入的是atmoic的拷贝就会失去原子性。