Go问答 关于 sync.Pool 的疑问

gowalker · 2019年06月05日 · 最后由 nobodynoname1943 回复于 2020年03月01日 · 332 次阅读

想在 pool 的基础上做一个限制池中对象数量的功能,但发现还是多次执行 pool.New

package main

import (
    "bytes"
    "log"
    "sync"
    "time"
)

const MaxFrameSize = 5000

func main() {
    for i := 0; i < 10; i++ {
        // 多个协程想从pool中拿到对象
        go func() {
            c := getBuf()
            putBuf(c)
            log.Println("put done")
        }()
    }

    time.Sleep(3 * time.Second)
}

var bufPool = sync.Pool{
    New: func() interface{} {
        log.Println("alloc")
        return bytes.NewBuffer(make([]byte, 0, MaxFrameSize))
    },
}

var bufPoolChan = make(chan bool, 1)

func getBuf() *bytes.Buffer {
    bufPoolChan <- true
    b := bufPool.Get().(*bytes.Buffer)
    b.Reset()
    return b
}

func putBuf(b *bytes.Buffer) {
    bufPool.Put(b)
    <-bufPoolChan
}

期望是只执行一次 NewBuffer,也就是只打印一次 alloc。 实际上每次执行,会打印多次 alloc,有大佬帮忙分析一波,如何限制 pool 中的对象数量吗?

更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio

sync.Pool 的源代码里说了,pool 里的对象随时都有可能被自动移除,并且没有任何通知。sync.Pool 的数量是不可控制的。

Pool 调用 New 与线程调度有关,Pool 内部有一个 localPool 的数组,每个 P 对应其中一个 localPool,在当前 P 执行 goroutine 的时候,优先从当前的 localPool 的 private 变量取,娶不到在从 shared 列表里面取,再取不到就尝试从别的 P 的 localPool 的 shared 里面偷一个。最后实在取不到就 New 一个。

由于你的 bufPoolChan 限制基本上 10 个 goroutine 就在两个 P 后面排队轮流执行,所以 alloc 就会出现两次,后面的基本就是从这两个 localPool 的 private 取出来的。

如果取消这个限制,10 个 goroutine 很快就被分配到 10 个 P 上去了,对应就有 10 个 localPool,10 次每次取 private 都取不到,取 shared 列表也取不到,别的 localPool 也没得偷,就会 New10 次,alloc 就会出现 10 次。

我觉得可以用 chann 解决

runtime.GOMAXPROCS(1)你可以在开头加上这个试一下,看 new 了几个,然后改为 2,再看一下。 看 put 的一段代码

func (p *Pool) Put(x interface{}) {
    if x == nil {
        return
    }
    l := p.pin()
    if l.private == nil {
        l.private = x
        x = nil
    }
    runtime_procUnpin()
    if x != nil {
        l.Lock()
        l.shared = append(l.shared, x)
        l.Unlock()
    }
}

首先会给 private,然后才会分配给 shared。private 是每个 P 私有的,所以即使已经把对象放回池子里去,别的 P 也取不到。

先说结论,为什么会出现多次 alloc?出现多次 alloc 是因为随着 GC 的推进,GC 把 Pool 里的对象清理掉了,为什么 GC 会清理 Pool 里的对象?防止缓存对象数量过多导致内存溢出。

关于更详细的 sync.Pool 的内容建议你看这篇文章:https://gocn.vip/topics/9921

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册