主頁 > 知識(shí)庫 > 7分鐘讀懂Go的臨時(shí)對象池pool以及其應(yīng)用場景

7分鐘讀懂Go的臨時(shí)對象池pool以及其應(yīng)用場景

熱門標(biāo)簽:服務(wù)外包 呼叫中心市場需求 地方門戶網(wǎng)站 鐵路電話系統(tǒng) Linux服務(wù)器 網(wǎng)站排名優(yōu)化 百度競價(jià)排名 AI電銷

臨時(shí)對象池 pool 是啥?

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

臨時(shí)對象池是一些可以分別存儲(chǔ)和取出的臨時(shí)對象。

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

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

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

在同一 package 中獨(dú)立運(yùn)行的多個(gè)獨(dú)立線程之間靜默共享一組臨時(shí)元素才是 pool 的合理使用場景。Pool 提供在多個(gè)獨(dú)立 client 之間共享臨時(shí)元素的機(jī)制。

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

另外,一些短生命周期的對象不適合使用 pool 來維護(hù),這種情況下使用 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緩存池大小

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

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

Get

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

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

func (p *Pool) Get() interface{} {
 if race.Enabled {
  race.Disable()
 }
 // 獲取本地 P 的 poolLocal 對象
 l := p.pin() 
 
 // 先獲取 private 池中的對象(只有一個(gè))
 x := l.private
 l.private = nil
 runtime_procUnpin()
 if x == nil {
  // 查找本地 shared 池,
  // 本地 shared 可能會(huì)被其他 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 {
   // 隨機(jī)把元素扔掉...
   // 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

當(dāng)世界暫停,垃圾回收將要開始時(shí), poolCleanup 會(huì)被調(diào)用。該函數(shù)內(nèi)不能分配內(nèi)存且不能調(diào)用任何運(yùn)行時(shí)函數(shù)。原因:
防止錯(cuò)誤的保留整個(gè) Pool

如果 GC 發(fā)生時(shí),某個(gè) goroutine 正在訪問 l.shared,整個(gè) Pool 將會(huì)保留,下次執(zhí)行時(shí)將會(huì)有雙倍內(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)用中,后臺(tái)在處理用戶的每條請求時(shí)都會(huì)為當(dāng)前請求創(chuàng)建一個(gè)上下文環(huán)境 Context,用于存儲(chǔ)請求信息及相應(yīng)信息等。Context 滿足長生命周期的特點(diǎn),且用戶請求也是屬于并發(fā)環(huán)境,所以對于線程安全的 Pool 非常適合用來維護(hù) Context 的臨時(shí)對象池。

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

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

初始化 engine 時(shí)定義了 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 也符合長生命周期的特點(diǎn),同時(shí)也會(huì)可能會(huì)在多 goroutine 中使用,所以也適合使用 pool 來維護(hù)。

printer 與 它的臨時(shí)對象池

// pp 用來維護(hù) 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)
}

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

您可能感興趣的文章:
  • Go語言中使用 buffered channel 實(shí)現(xiàn)線程安全的 pool
  • Go語言學(xué)習(xí)技巧之如何合理使用Pool

標(biāo)簽:蘭州 崇左 黃山 湖南 銅川 衡水 仙桃 湘潭

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《7分鐘讀懂Go的臨時(shí)對象池pool以及其應(yīng)用場景》,本文關(guān)鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 收縮
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266