最近一个项目中某个对象需要持有一个统计对象,并且需要能够原子的更新这个统计对象(并发读写场景下),避免data race。为了避免对象的copy, 肯定是要持有这个统计对象的指针了。 此外,这个统计对象其实是统计的模型,需要能够随时替换成其余的统计实现。所以很自然的选用了 interface{}来保存。
type Obj struct {
data interface{
}
}
但是有个问题,golang 里面的atomic没有提供方法来实现interface{}的原子读写。但是atomic提供了 atomic.Value. 该类型可以原子更新 interface{}。
数据结构
先看数据结构的定义:
// A Value provides an atomic load and store of a consistently typed value.
// The zero value for a Value returns nil from Load.
// Once Store has been called, a Value must not be copied.
// A Value must not be copied after first use.
type Value struct {
v interface{
}
}
// ifaceWords is interface{} internal representation.
type ifaceWords struct {
typ unsafe.Pointer
data unsafe.Pointer
}
atomic.Value
里面其实维护的就是一个 interface{}. 然后提供原子的更新这个interface{}的方法。
注意注释所说的,提供的是原子的Store 和 load 一个类型一致的value。也就是说 atomic.Value 一旦第一次Store了一个value, 那么后面的Store就必须是同一个类型的value。然后就是在store之后,Value不能被复制。
注意到下面还有一个结构:ifaceWords
,这个其实是对 interface{} 内部结构的表示。我们知道一个interface{}会被编译编译成 eface 结构或则 iface 结构。定义在 runtime/runtime2.go
里面。eface表示一个空接口,iface描述的是非空接口,它包含方法。
type iface struct {
tab *itab
data unsafe.Pointer
}
type eface struct {
_type *_type
data unsafe.Pointer
}
不管是 iface 还是 eface 里面保存的都是两个指针对象。所以我们可以把 interface{} 对象的指针转换成*ifaceWords
,这与后面的Store 和 Load息息相关。
关于非类型安全的指针转换unsafe.Pointer,可以参考这篇文章: