7分钟读懂Go的临时对象池pool以及其应用场景
临时对象池pool是啥?
sync.Pool给了一大段注释来说明pool是啥,我们看看这段都说了些什么。
临时对象池是一些可以分别存储和取出的临时对象。
池中的对象会在没有任何通知的情况下被移出(释放或者重新取出使用)。如果pool中持有某个对象的唯一引用,则该对象很可能会被回收。
Pool在多goroutine使用环境中是安全的。
Pool是用来缓存已经申请了的目前未使用的接下来可能会使用的内存,以此缓解GC压力。使用它可以方便高效的构建线程安全的freelist(一种用于动态内存申请的数据结构)。然而,它并不适合所有场景的freelist。
在同一package中独立运行的多个独立线程之间静默共享一组临时元素才是pool的合理使用场景。Pool提供在多个独立client之间共享临时元素的机制。
在fmt包中有一个使用Pool的例子,它维护了一个动态大小的输出buffer。
另外,一些短生命周期的对象不适合使用pool来维护,这种情况下使用pool不划算。这是应该使用它们自己的freelist(这里可能指的是go内存模型中用于缓存<32k小对象的freelist)更高效。
Pool一旦使用,不能被复制。
Pool结构体的定义为:
typePoolstruct{ noCopynoCopy localunsafe.Pointer//本地P缓存池指针 localSizeuintptr//本地P缓存池大小 //当池中没有可能对象时 //会调用New函数构造构造一个对象 Newfunc()interface{} }
Pool中有两个定义的公共方法,分别是Put-向池中添加元素;Get-从池中获取元素,如果没有,则调用New生成元素,如果New未设置,则返回nil。
Get
Pool会为每个P维护一个本地池,P的本地池分为私有池private和共享池shared。私有池中的元素只能本地P使用,共享池中的元素可能会被其他P偷走,所以使用私有池private时不用加锁,而使用共享池shared时需加锁。
Get会优先查找本地private,再查找本地shared,最后查找其他P的shared,如果以上全部没有可用元素,最后会调用New函数获取新元素。
func(p*Pool)Get()interface{}{ ifrace.Enabled{ race.Disable() } //获取本地P的poolLocal对象 l:=p.pin() //先获取private池中的对象(只有一个) x:=l.private l.private=nil runtime_procUnpin() ifx==nil{ //查找本地shared池, //本地shared可能会被其他P访问 //需要加锁 l.Lock() last:=len(l.shared)-1 iflast>=0{ x=l.shared[last] l.shared=l.shared[:last] } l.Unlock() //查找其他P的shared池 ifx==nil{ x=p.getSlow() } } ifrace.Enabled{ race.Enable() ifx!=nil{ race.Acquire(poolRaceAddr(x)) } } //未找到可用元素,调用New生成 ifx==nil&&p.New!=nil{ x=p.New() } returnx }
getSlow,从其他P中的shared池中获取可用元素:
func(p*Pool)getSlow()(xinterface{}){ //Seethecommentinpinregardingorderingoftheloads. size:=atomic.LoadUintptr(&p.localSize)//load-acquire local:=p.local//load-consume //Trytostealoneelementfromotherprocs. pid:=runtime_procPin() runtime_procUnpin() fori:=0;i=0{ x=l.shared[last] l.shared=l.shared[:last] l.Unlock() break } l.Unlock() } returnx }
Put
Put优先把元素放在private池中;如果private不为空,则放在shared池中。有趣的是,在入池之前,该元素有1/4可能被丢掉。
func(p*Pool)Put(xinterface{}){ ifx==nil{ return } ifrace.Enabled{ iffastrand()%4==0{ //随机把元素扔掉... //Randomlydropxonfloor. return } race.ReleaseMerge(poolRaceAddr(x)) race.Disable() } l:=p.pin() ifl.private==nil{ l.private=x x=nil } runtime_procUnpin() ifx!=nil{ //共享池访问,需要加锁 l.Lock() l.shared=append(l.shared,x) l.Unlock() } ifrace.Enabled{ race.Enable() } }
poolCleanup
当世界暂停,垃圾回收将要开始时,poolCleanup会被调用。该函数内不能分配内存且不能调用任何运行时函数。原因:
防止错误的保留整个Pool
如果GC发生时,某个goroutine正在访问l.shared,整个Pool将会保留,下次执行时将会有双倍内存
funcpoolCleanup(){ fori,p:=rangeallPools{ allPools[i]=nil fori:=0;i案例1:gin中的Contextpool
在web应用中,后台在处理用户的每条请求时都会为当前请求创建一个上下文环境Context,用于存储请求信息及相应信息等。Context满足长生命周期的特点,且用户请求也是属于并发环境,所以对于线程安全的Pool非常适合用来维护Context的临时对象池。
Gin在结构体Engine中定义了一个pool:
typeEnginestruct{ //...省略了其他字段 poolsync.Pool }初始化engine时定义了pool的New函数:
engine.pool.New=func()interface{}{ returnengine.allocateContext() } //allocateContext func(engine*Engine)allocateContext()*Context{ //构造新的上下文对象 return&Context{engine:engine} }ServeHttp:
//从pool中获取,并转化为*Context c:=engine.pool.Get().(*Context) c.writermem.reset(w) c.Request=req c.reset()//reset engine.handleHTTPRequest(c) //再扔回pool中 engine.pool.Put(c)案例2:fmt中的printerpool
printer也符合长生命周期的特点,同时也会可能会在多goroutine中使用,所以也适合使用pool来维护。
printer与它的临时对象池
//pp用来维护printer的状态 //它通过sync.Pool来重用,避免申请内存 typeppstruct{ //...字段已省略 } varppFree=sync.Pool{ New:func()interface{}{returnnew(pp)}, }获取与释放:
funcnewPrinter()*pp{ p:=ppFree.Get().(*pp) p.panicking=false p.erroring=false p.fmt.init(&p.buf) returnp } func(p*pp)free(){ p.buf=p.buf[:0] p.arg=nil p.value=reflect.Value{} ppFree.Put(p) }总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。