Go源码阅读:sync.Pool-创新互联
sync.Pool
管理一组可以单独保存和检索的临时对象。目的是缓存已分配但未使用的对象,以供以后重用,从而减轻GC的压力。核心就是Put
、Get
和New
使用sync.Pool
需要提供一个New
方法,以便在池中没有缓存的对象时,调用New
创建
type Pool struct {noCopy noCopy // 静态检查机制:内置noCopy结构体的对象在第一次使用后不会再发生复制
local unsafe.Pointer // local 固定大小 per-P 池, 实际类型为[P]poolLocal
localSize uintptr // local array 的大小
victim unsafe.Pointer // 在上一个GC周期local被poolCleanup函数放置于此,它可能尚未被清理。 后面再讲
victimSize uintptr // victims array 的大小
// 在Get方法失败的情况下,选择性的创建一个值
New func() interface{}
}
poolLocal结构体type poolLocalInternal struct {private interface{} // 只能被各自的P使用
shared poolChain // 可以被任意P使用
}
type poolLocal struct {poolLocalInternal
// 对齐到机器的缓存行大小,以避免false sharing [1]
pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}
private
只保存一个对象,且只能被拥有当前poolLocal
的P
访问shared
是一个链表,可以被其他P
访问
有了上面这张图,Get
如何从池中获取对象,也能猜个七七八八了。
- 优先选择当前
P
对应的poolLocal.private
。 - 若取不到,优先从当前
P
的share
的head
链表的头部取出一个对象。 - 若取不到,则尝试从其他线程中
share
的tail
中steal一个对象。 - 若取不到,则调用
New
创建一个对象。
func (p *Pool) Get() interface{} {// 如果启用的 race 检查则先停用
if race.Enabled {race.Disable()
}
// 返回pid,和 poolLocal
l, pid := p.pin()
// 尝试从private中获取数据
x := l.private
// 获取之后将private置nil,相当于从poolLocal中移除对象
l.private = nil
// 若从private中获取失败
if x == nil {// 为了更好的利用时间局部性,从 shared 头部读取对象
x, _ = l.shared.popHead()
// 如果读取不到,则steal获取新的缓存对象
if x == nil { x = p.getSlow(pid)
}
}
runtime_procUnpin()
// 恢复 race 检查
if race.Enabled {race.Enable()
if x != nil { race.Acquire(poolRaceAddr(x))
}
}
// 若还是取不出来则调用New 创建
if x == nil && p.New != nil {x = p.New()
}
return x
}
窃取对象窃取的策略
- 尝试从其他p的
shared
窃取。 - 若取不到,尝试从上一轮GC时被放置(惰性回收)的
poolLocal
的private
取对象。 - 若取不到,尝试从上一轮GC时被放置(惰性回收)的
poolLocal
的shared
取对象。 - 还是取不到,返回nil,有
Get
函数调用New
创建一个对象。
func (p *Pool) getSlow(pid int) interface{} {// 遍历所有p的 poolLocal 尝试从shared中窃取一个对象
size := runtime_LoadAcquintptr(&p.localSize) // load-acquire
locals := p.local // load-consume
// Try to steal one element from other procs.
for i := 0; i< int(size); i++ {l := indexLocal(locals, (pid+i+1)%int(size))
if x, _ := l.shared.popTail(); x != nil { return x
}
}
// 遍历所有p的victim
size = atomic.LoadUintptr(&p.victimSize)
if uintptr(pid) >= size {return nil
}
// 遍历当前p的victim的private
locals = p.victim
l := indexLocal(locals, pid)
if x := l.private; x != nil {l.private = nil
return x
}
// 遍历其他p的victim的shared
for i := 0; i< int(size); i++ {l := indexLocal(locals, (pid+i)%int(size))
if x, _ := l.shared.popTail(); x != nil { return x
}
}
atomic.StoreUintptr(&p.victimSize, 0)
return nil
}
Put将一个(不确定对象状态)的对象放入到池中,遵循以下策略。
- 优先放入
private
- 如果
private
已经有值,则尝试放入shared
func (p *Pool) Put(x interface{}) {if x == nil { return
}
// 停用 race
if race.Enabled { if fastrand()%4 == 0 { // Randomly drop x on floor.
return
}
race.ReleaseMerge(poolRaceAddr(x))
race.Disable()
}
// 获取 localPool
l, _ := p.pin()
// 优先放入 private
if l.private == nil { l.private = x
x = nil
}
// 如果不能放入 private 则放入 shared
if x != nil { l.shared.pushHead(x)
}
runtime_procUnpin()
// 恢复race
if race.Enabled { race.Enable()
}
}
惰性回收sync.Pool 的垃圾回收发生在运行时 GC 开始之前。
var poolcleanup func()
// 利用编译器标志将 sync 包中的清理注册到运行时
//go:linkname sync_runtime_registerPoolCleanup sync.runtime_registerPoolCleanup
func sync_runtime_registerPoolCleanup(f func()) {poolcleanup = f
}
// 实现缓存清理
func clearpools() {// clear sync.Pools
if poolcleanup != nil {poolcleanup()
}
(...)
}
清理函数victim
的使用出现在getslow
函数中,当从其他P
的shared
中无法窃取到对象时,会尝试从上一次GC周期时放置的缓存中获取对象。
这就涉及到了惰性回收,当GC触发前poolCleanup
会将运行时中所有sync.Pool
对象中的poolLocal
移动到其对应victim
字段,victim
会保存一个GC周期后被清除。
func poolCleanup() {// 清空上一GC周期的victim缓存
for _, p := range oldPools { p.victim = nil
p.victimSize = 0
}
// 将当前运行时中所有Pool(不同的Pool对象)中的local移动到其victim中
for _, p := range allPools { p.victim = p.local
p.victimSize = p.localSize
p.local = nil
p.localSize = 0
}
// 互换oldPool和allPools,并将allPools置nil
oldPools, allPools = allPools, nil
}
备注才疏学浅,若有疑惑之处,很可能是笔者出错了。还望不吝赐教。
参考资料[1] false sharing
2 欧老师:Go source study: sync.Pool
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
文章名称:Go源码阅读:sync.Pool-创新互联
文章来源:http://scjbc.cn/article/dshsps.html