临时对象池 pool 是啥?
sync.Pool
给了一大段注释来说明 pool
是啥,我们看看这段都说了些什么。
临时对象池是一些可以分别存储和取出的临时对象。
池中的对象会在没有任何通知的情况下被移出(释放或者重新取出使用)。如果 pool
中持有某个对象的唯一引用,则该对象很可能会被回收。
Pool
在多 goroutine
使用环境中是安全的。
Pool
是用来缓存已经申请了的 目前未使用的 接下来可能会使用的 内存,以此缓解 GC
压力。使用它可以方便高效的构建线程安全的 free list
(一种用于动态内存申请的数据结构)。然而,它并不适合所有场景的 free list
。
在同一 package
中独立运行的多个独立线程之间静默共享一组临时元素才是 pool
的合理使用场景。Pool
提供在多个独立 client
之间共享临时元素的机制。
在 fmt
包中有一个使用 Pool
的例子,它维护了一个动态大小的输出 buffer
。
另外,一些短生命周期的对象不适合使用 pool
来维护,这种情况下使用 pool
不划算。这是应该使用它们自己的 free list
(这里可能指的是 go 内存模型中用于缓存 <32k小对象的 free list) 更高效。
Pool
一旦使用,不能被复制。
Pool
结构体的定义为:
type Pool struct {
noCopy noCopy
local unsafe.Pointer // 本地P缓存池指针
localSize uintptr // 本地P缓存池大小
// 当池中没有可能对象时
// 会调用 New 函数构造构造一个对象
New func() 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{} {
if race.Enabled {
race.Disable()
}
// 获取本地 P 的 poolLocal 对象
l := p.pin()
// 先获取 private 池中的对象(只有一个)
x := l.private
l.private = nil
runtime_procUnpin()
if x == nil {
// 查找本地 shared 池,
// 本地 shared 可能会被其他 P 访问
// 需要加锁
l.Lock()
last := len(l.shared) - 1
if last >= 0 {
x = l.shared[last]
l.shared = l.shared[:last]
}
l.Unlock()
// 查找其他 P 的 shared 池
if x == nil {
x = p.getSlow()
}
}
if race.Enabled {
race.Enable()
if x != nil {
race.Acquire(poolRaceAddr(x))
}
}
// 未找到可用元素,调用 New 生成
if x == nil && p.New != nil {
x = p.New()
}
return x
}
getSlow
,从其他 P
中的 shared
池中获取可用元素:
func (p *Pool) getSlow() (x interface{}) {
// See the comment in pin regarding ordering of the loads.
size := atomic.LoadUintptr(&p.localSize) // load-acquire
local := p.local // load-consume
// Try to steal one element from other procs.
pid := runtime_procPin()
runtime_procUnpin()
for i := 0; i < int(size); i++ {
l := indexLocal(local, (pid+i+1)%int(size))
// 对应 pool 需加锁
l.Lock()
last := len(l.shared) - 1
if last >= 0 {
x = l.shared[last]
l.shared = l.shared[:last]
l.Unlock()
break
}
l.Unlock()
}
return x
}
Put
Put
优先把元素放在 private
池中;如果 private
不为空,则放在 shared
池中。有趣的是,在入池之前,该元素有 1/4 可能被丢掉。
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
}
runtime_procUnpin()
if x != nil {
// 共享池访问,需要加锁
l.Lock()
l.shared = append(l.shared, x)
l.Unlock()
}
if race.Enabled {
race.Enable()
}
}
poolCleanup
当世界暂停,垃圾回收将要开始时, poolCleanup
会被调用。该函数内不能分配内存且不能调用任何运行时函数。原因:
防止错误的保留整个 Pool
如果 GC
发生时,某个 goroutine
正在访问 l.shared
,整个 Pool
将会保留,下次执行时将会有双倍内存
func poolCleanup() {
for i, p := range allPools {
allPools[i] = nil
for i := 0; i < int(p.localSize); i++ {
l := indexLocal(p.local, i)
l.private = nil
for j := range l.shared {
l.shared[j] = nil
}
l.shared = nil
}
p.local = nil
p.localSize = 0
}
allPools = []*Pool{}
}
案例1:gin 中的 Context pool
在 web
应用中,后台在处理用户的每条请求时都会为当前请求创建一个上下文环境 Context
,用于存储请求信息及相应信息等。Context
满足长生命周期的特点,且用户请求也是属于并发环境,所以对于线程安全的 Pool
非常适合用来维护 Context
的临时对象池。
Gin
在结构体 Engine
中定义了一个 pool
:
type Engine struct {
// ... 省略了其他字段
pool sync.Pool
}
初始化 engine
时定义了 pool
的 New
函数:
engine.pool.New = func() interface{} {
return engine.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 中的 printer pool
printer
也符合长生命周期的特点,同时也会可能会在多 goroutine
中使用,所以也适合使用 pool
来维护。
printer
与 它的临时对象池
// pp 用来维护 printer 的状态
// 它通过 sync.Pool 来重用,避免申请内存
type pp struct {
//... 字段已省略
}
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() {
p.buf = p.buf[:0]
p.arg = nil
p.value = reflect.Value{}
ppFree.Put(p)
}
有疑问加站长微信联系(非本文作者)