在 golang 中有一个池, 它特别神奇, 你只要和它有个约定, 你要什么它就给什么, 你用完了还可以还回去, 但是下次拿的时候呢, 确不一定是你上次存的那个, 这个池就是 sync.Pool。sync.Pool 类型只有两个方法——Put 和 Get。Put 用于在当前的池中存放临时对象, 它接受一个 interface{}类型的参数; 而 Get 则被用于从当前的池中获取临时对象, 它会返回一个 interface{}类型的值。更具体地说, 这个类型的 Get 方法可能会从当前的池中删除掉任何一个值, 然后把这个值作为结果返回。如果此时当前的池中没有任何值, 那么这个方法就会使用当前池的 New 字段创建一个新值, 并直接将其返回, 先看个简单的示例:
var strPool = sync.Pool{ New: func() interface{} { return "test str" },}func main() { str := strPool.Get() fmt.Println(str) strPool.Put(str)}
通过 New 去定义你这个池子里面放的究竟是什么东西, 在这个池子里面你只能放一种类型的东西, 比如在上面的例子中我就在池子里面放了字符串。我们随时可以通过 Get 方法从池子里面获取我们之前在 New 里面定义类型的数据, 当我们用完了之后可以通过 Put 方法放回去, 或者放别的同类型的数据进去。那么这个池子的目的是什么呢? 其实一句话就可以说明白, 就是为了复用已经使用过的对象, 来达到优化内存使用和回收的目的。说白了, 一开始这个池子会初始化一些对象供你使用, 如果不够了呢, 自己会通过 new 产生一些, 当你放回去了之后这些对象会被别人进行复用, 当对象特别大并且使用非常频繁的时候可以大大的减少对象的创建和回收的时间。
type Pool struct { noCopy noCopy local unsafe.Pointer // 数组指针, 指向 [P]poolLocal localSize uintptr // 大小为 P victim unsafe.Pointer // 用于存放"幸存者" victimSize uintptr // "幸存者"size // New optionally specifies a function to generate // a value when Get would otherwise return nil. // It may not be changed concurrently with calls to Get. New func() interface{}}type poolLocalInternal struct { private interface{} // Can be used only by the respective P. shared poolChain // Local P can pushHead/popHead; any P can popTail.}type poolLocal struct { poolLocalInternal // Prevents false sharing on widespread platforms with // 128 mod (cache line size) = 0 . pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte}
我们可以看到其实结构并不复杂, 但是如果自己看的话有点懵, 注意几个细节就可以:
func (p *Pool) Get() interface{} { // ...... l, pid := p.pin() // Step1: 先直接获取自己的 private, 如果有, 直接返回 x := l.private l.private = nil if x == nil { // Step2: 如果 private 为空, 就从自己的 shared 随便取一个 x, _ = l.shared.popHead() if x == nil { x = p.getSlow(pid) } } runtime_procUnpin() // ...... if x == nil && p.New != nil { // Step5: 找了一圈都没有, 自己 New 一个 x = p.New() } return x}
func (p *Pool) getSlow(pid int) interface{} { size := atomic.LoadUintptr(&p.localSize) locals := p.local for i := 0; i < int(size); i++ { l := indexLocal(locals, (pid+i+1)%int(size)) // Step3: 从其它的 P 中随便偷一个出来 if x, _ := l.shared.popTail(); x != nil { return x } } size = atomic.LoadUintptr(&p.victimSize) if uintptr(pid) >= size { return nil } // Step4: 从"幸存者"中找一个, 找的逻辑和前面的一样, 先 private, 再 shared locals = p.victim l := indexLocal(locals, pid) if x := l.private; x != nil { l.private = nil return x } 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}
我去掉了其中一些竞态分析的代码, 代码里面我也标明了每个 step, Get 的逻辑其实非常清晰:
func (p *Pool) Put(x interface{}) { if x == nil { return } // ...... l, _ := p.pin() if l.private == nil { l.private = x x = nil } if x != nil { l.shared.pushHead(x) } runtime_procUnpin() // ......}
Put 主要做 2 件事情:
我们先回顾一下 GMP 的知识:
pool 在掌握基础用法的同时, 需要知道 Get 和 Push 方法的实现逻辑, 其中最重要的一点, 是需要将 pool 和 GMP 的调度原理结合起来, 其中两者的 P 的原理其实是一样的, 只是对于资源抢占这一块, GMP 抢占的是 G, pool 抢占的是 pool 数据, 对于这块, 其实是自己个人的理解, 如果理解的不对, 还请大家帮忙指出。