内容简介:临时对象池是一些可以分别存储和取出的临时对象。池中的对象会在没有任何通知的情况下被移出(释放或者重新取出使用)。如果
临时对象池 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) }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
网络多人游戏架构与编程
格雷泽 (Joshua Glazer)、马达夫 (Sanjay Madhav) / 王晓慧、张国鑫 / 人民邮电出版社 / 2017-10-1 / CNY 109.00
本书是一本深入探讨关于网络多人游戏编程的图书。 全书分为13章,从网络游戏的基本概念、互联网、伯克利套接字、对象序列化、对象复制、网络拓扑和游戏案例、延迟、抖动和可靠性、改进的延迟处理、可扩展性、安全性、真实世界的引擎、玩家服务、云托管专用服务器等方面深入介绍了网络多人游戏开发的知识,既全面又详尽地剖析了众多核心概念。 本书的多数示例基于C++编写,适合对C++有一定了解的读者阅读。本......一起来看看 《网络多人游戏架构与编程》 这本书的介绍吧!