golang是一个自动垃圾回收的语言,创建对象的时候无需关心他的回收,但是由于垃圾回收机制有一个STW(stop-the-word,暂停时间),频繁的创建和清理对程序的性能还是有影响的。所以我们可以讲需要用到的对象存起来当缓存来使用。
sync.pool是golang标准库中提供的一个通用的数据结构,可以创建池化的对象。把不用的对象回收,避免被清理,使用的时候直接取用。
sync.pool主要有两个方法Get和Put,以及一个New字段。
New的字段类型是函数func() interface{},可以用来创建临时对象。
Get方法会从池中取走一个元素,返回一个intrtface{}类型,也有可能返回nil(New字段未设置,有没有空闲的元素返回)
Put方法会接受一个interface{}类型的元素,将元素返回给Pool,如果是nil,则会忽略
这是fmt包中使用到sync.Pool的代码。会创建一个用于缓存临时对象的ppFree,可以识别、格式化、暂存需要打印的内容。
ppFree的New字段在被调用的时候,总是会返回一个全新的pp类型的指针,保证了ppFree的Get方法总能返回一个可以包含需要打印内容值。同时,在使用完临时对象后,会抹掉缓存的内容,再存到ppFree中。
var ppFree = sync.Pool{
New: func() interface{} { return new(pp) },
}
func newPrinter() *pp {
p := ppFree.Get().(*pp)
p.panicking = false
p.erroring = false
p.fmt.init(&p.buf)
return p
}
func (p *pp) free() {
if cap(p.buf) > 64<<10 {
return
}
p.buf = p.buf[:0]
p.arg = nil
p.value = reflect.Value{}
ppFree.Put(p)
}
fmt.Println、fmt.Printf等打印函数都是如此使用ppFree,以及其中的临时对象的。因此,在程序同时执行很多的打印函数调用的时候,ppFree可以及时地把它缓存的临时对象提供给它们,以加快执行的速度。而当程序在一段时间内不再执行打印函数调用时,ppFree中的临时对象又能够被及时地清理掉,以节省内存空间。
除了上述的部分之外,Pool还有local和victim两个字段,这两个字段主要用来存储空闲元素。每次垃圾回收的时候,Pool会把victim中的对象移除,然后把local的数据给victim,清空local。如果在下次垃圾回收之前victim中的元素有被Get,该元素就会被移到local中,如果没有被Get,victim中的元素将会被回收。
func poolCleanup() {
// Drop victim caches from all pools.
for _, p := range oldPools {
p.victim = nil
p.victimSize = 0
}
// Move primary cache to victim cache.
for _, p := range allPools {
p.victim = p.local
p.victimSize = p.localSize
p.local = nil
p.localSize = 0
}
oldPools, allPools = allPools, nil
}
Get方法
func (p *Pool) Get() interface{} {
if race.Enabled {
race.Disable()
}
l, pid := p.pin()
x := l.private
l.private = nil
if x == nil {
x, _ = l.shared.popHead()
if x == nil {
x = p.getSlow(pid)
}
}
runtime_procUnpin()
if race.Enabled {
race.Enable()
if x != nil {
race.Acquire(poolRaceAddr(x))
}
}
if x == nil && p.New != nil {
x = p.New()
}
return x
}
get方法首先会把goroutine固定在当前的P上,避免查找期间被其他的P执行。然后会先从本地的private字段中获取可用元素,如果没有获取到就会尝试从本地的shared中获取一个,如果还是没有,就会使用getSlow方法去其他shared中哪一个。最后如果都没有获取到,就会使用New函数新建一个。
Put方法
func (p *Pool) Put(x interface{}) {
if x == nil {
return
}
if race.Enabled {
if fastrand()%4 == 0 {
// Randomly drop x on floor.
return
}
race.ReleaseMerge(poolRaceAddr(x))
race.Disable()
}
l, _ := p.pin()
if l.private == nil {
l.private = x
x = nil
}
if x != nil {
l.shared.pushHead(x)
}
runtime_procUnpin()
if race.Enabled {
race.Enable()
}
}
put会优先存到本地 private,如果 private 字段已经有值了,那么就把此元素 push 到本地队列中。
有疑问加站长微信联系(非本文作者)