溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Go臨時對象池pool怎么用

發(fā)布時間:2021-09-15 09:51:15 來源:億速云 閱讀:94 作者:小新 欄目:編程語言

這篇文章給大家分享的是有關(guān)Go臨時對象池pool怎么用的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

臨時對象池 pool 是啥?

sync.Pool 給了一大段注釋來說明 pool 是啥,我們看看這段都說了些什么。

臨時對象池是一些可以分別存儲和取出的臨時對象。

池中的對象會在沒有任何通知的情況下被移出(釋放或者重新取出使用)。如果 pool 中持有某個對象的唯一引用,則該對象很可能會被回收。

Pool 在多 goroutine 使用環(huán)境中是安全的。

Pool 是用來緩存已經(jīng)申請了的 目前未使用的 接下來可能會使用的 內(nèi)存,以此緩解 GC 壓力。使用它可以方便高效的構(gòu)建線程安全的 free list(一種用于動態(tài)內(nèi)存申請的數(shù)據(jù)結(jié)構(gòu))。然而,它并不適合所有場景的 free list。

在同一 package 中獨立運行的多個獨立線程之間靜默共享一組臨時元素才是 pool 的合理使用場景。Pool 提供在多個獨立 client 之間共享臨時元素的機制。

在 fmt 包中有一個使用 Pool 的例子,它維護了一個動態(tài)大小的輸出 buffer。

另外,一些短生命周期的對象不適合使用 pool 來維護,這種情況下使用 pool 不劃算。這是應(yīng)該使用它們自己的 free list(這里可能指的是 go 內(nèi)存模型中用于緩存 <32k小對象的 free list) 更高效。

Pool 一旦使用,不能被復(fù)制。

Pool 結(jié)構(gòu)體的定義為:

type Pool struct {
 noCopy noCopy

 local  unsafe.Pointer // 本地P緩存池指針
 localSize uintptr  // 本地P緩存池大小

 // 當池中沒有可能對象時
 // 會調(diào)用 New 函數(shù)構(gòu)造構(gòu)造一個對象
 New func() interface{}
}

Pool 中有兩個定義的公共方法,分別是 Put - 向池中添加元素;Get - 從池中獲取元素,如果沒有,則調(diào)用 New 生成元素,如果 New 未設(shè)置,則返回 nil。

Get

Pool 會為每個 P 維護一個本地池,P 的本地池分為 私有池 private 和共享池 shared。私有池中的元素只能本地 P 使用,共享池中的元素可能會被其他 P 偷走,所以使用私有池 private 時不用加鎖,而使用共享池 shared 時需加鎖。

Get 會優(yōu)先查找本地 private,再查找本地 shared,最后查找其他 P 的 shared,如果以上全部沒有可用元素,最后會調(diào)用 New 函數(shù)獲取新元素。

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))
  }
 }
 // 未找到可用元素,調(diào)用 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))
  // 對應(yīng) 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 優(yōu)先把元素放在 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 會被調(diào)用。該函數(shù)內(nèi)不能分配內(nèi)存且不能調(diào)用任何運行時函數(shù)。原因:
防止錯誤的保留整個 Pool

如果 GC 發(fā)生時,某個 goroutine 正在訪問 l.shared,整個 Pool 將會保留,下次執(zhí)行時將會有雙倍內(nèi)存

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 應(yīng)用中,后臺在處理用戶的每條請求時都會為當前請求創(chuàng)建一個上下文環(huán)境 Context,用于存儲請求信息及相應(yīng)信息等。Context 滿足長生命周期的特點,且用戶請求也是屬于并發(fā)環(huán)境,所以對于線程安全的 Pool 非常適合用來維護 Context 的臨時對象池。

Gin 在結(jié)構(gòu)體 Engine 中定義了一個 pool:

type Engine struct {
 // ... 省略了其他字段
 pool    sync.Pool
}

初始化 engine 時定義了 pool 的 New 函數(shù):

engine.pool.New = func() interface{} {
 return engine.allocateContext()
}

// allocateContext
func (engine *Engine) allocateContext() *Context {
 // 構(gòu)造新的上下文對象
 return &Context{engine: engine}
}

ServeHttp:

// 從 pool 中獲取,并轉(zhuǎn)化為 *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 的狀態(tài)
// 它通過 sync.Pool 來重用,避免申請內(nèi)存
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)
}

感謝各位的閱讀!關(guān)于“Go臨時對象池pool怎么用”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI