概述
Go的sync/atomic包提供了原子操作,支持的数据类型包括:
int32, int64, uint32, uint64, uintptr, unsafe.Pointer
1
若需要扩大原子操作的适用范围,可以使用atomic包中的Value。利用它可以实现对任意值进行原子得存储与加载。
使用注意点
atomic.Value只有两个指针方法:Store、Load。使用时需要遵循两个原则:1.不能存储nil;2.存储第一个值后,就只能存储这个类型的值。
看一下atom.Value的实现可以发现一个内部的结构ifaceWords,使用unsafe.Pointer存储数据类型及内容得指针
type ifaceWords struct {
typ unsafe.Pointer
data unsafe.Pointer
}
1
2
3
4
在使用Store进行存储时,首先判断待存储值是否为nil,若为nil会直接panic。之后会读取typ,若为nil则会将待存储值得类型、值的指针分别对typ、data进行赋值;若不为nil,则会判断待存储值的类型是否与既有的typ一致,不一致也会引起panic,一致的话则是将新值的指针赋予data。
为了防止在使用是意外出现panic,所以可以考虑在外部先进行合法性校验。
引用类型带来的坑点
因为atom.Value内部实际上维护的是存储值的指针,而这个指针因为不对外暴露,所以认为是并发安全的。然而如果尝试用它来存储引用类型,维护的就是这个引用类型的指针,则不能保证实际的数据是并发安全的。举个例子:
uint32是值类型,切片[]uint32是引用类型,我们使用这样两个函数来尝试修改值。
//值类型
func atomic_value(a uint32) {
var v atomic.Value
v.Store(a)
a = 666
fmt.Println(a)
fmt.Println(v.Load())
}
// 引用类型
func atomic_slice(s []uint32) {
var v atomic.Value
v.Store(s)
s[0] = 666
fmt.Println(s)
fmt.Println(v.Load())
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
然后我们分别调用这两个函数
func TestAtomValue(t *testing.T) {
atomic_value(1)
atomic_slice([]uint32{1,2,3})
}
1
2
3
4
最后它的输出是这样的:
可以看到我们使用Store存入uint32的值a后,我们无论怎么在外部修改a,使用Load都可以获取到我们Store的值。
然而我们若使用Store存入引用类型的切片,我们在外部修改值,Load出来的值也会收到影响。这是因为对于一个引用类型,我们实际上只是Store了一个指针,只是对一个指针的原子操作,而这个指针实际指向的地址的值,并不在atomic.Value的维护下,所以并不是并发安全的。
总结
1.atomic.Value可以实现对自定义类型的原子操作
2.不能存入nil
3.对于同一个atomic.Value不能存入类型不同的值
4.最好不要使用atomic.Value存储引用类型的值,可能导致数据不是并发安全的
————————————————
版权声明:本文为CSDN博主「雨儿酱在鹿上」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/q895431756/article/details/111063656