golang sync.Pool使用

陈陈陈_6150 · · 136 次点击 · · 开始浏览    

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 到本地队列中。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:陈陈陈_6150

查看原文:golang sync.Pool使用

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:1006366459

136 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传