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

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

熱門標(biāo)簽:浙江高速公路地圖標(biāo)注 江西轉(zhuǎn)化率高的羿智云外呼系統(tǒng) 南通如皋申請(qǐng)開(kāi)通400電話 學(xué)海導(dǎo)航地圖標(biāo)注 西部云谷一期地圖標(biāo)注 地圖標(biāo)注的汽車標(biāo) 中國(guó)地圖標(biāo)注省會(huì)高清 高德地圖標(biāo)注口訣 廣州呼叫中心外呼系統(tǒng)

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

sync.Pool 給了一大段注釋來(lái)說(shuō)明 pool 是啥,我們看看這段都說(shuō)了些什么。

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

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

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

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

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

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

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

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

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

type Pool struct {
 noCopy noCopy

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

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

Pool 中有兩個(gè)定義的公共方法,分別是 Put - 向池中添加元素;Get - 從池中獲取元素,如果沒(méi)有,則調(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,如果以上全部沒(méi)有可用元素,最后會(huì)調(diào)用 New 函數(shù)獲取新元素。

func (p *Pool) Get() interface{} {
 if race.Enabled {
  race.Disable()
 }
 // 獲取本地 P 的 poolLocal 對(duì)象
 l := p.pin() 
 
 // 先獲取 private 池中的對(duì)象(只有一個(gè))
 x := l.private
 l.private = nil
 runtime_procUnpin()
 if x == nil {
  // 查找本地 shared 池,
  // 本地 shared 可能會(huì)被其他 P 訪問(wèn)
  // 需要加鎖
  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))
  // 對(duì)應(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 {
  // 共享池訪問(wèn),需要加鎖
  l.Lock()
  l.shared = append(l.shared, x)
  l.Unlock()
 }
 if race.Enabled {
  race.Enable()
 }
}

poolCleanup

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

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

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)造新的上下文對(duì)象
 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 也符合長(zhǎng)生命周期的特點(diǎn),同時(shí)也會(huì)可能會(huì)在多 goroutine 中使用,所以也適合使用 pool 來(lái)維護(hù)。

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

// pp 用來(lái)維護(hù) printer 的狀態(tài)
// 它通過(guò) sync.Pool 來(lái)重用,避免申請(qǐng)內(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é)

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

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

標(biāo)簽:德宏 東營(yíng) 保定 曲靖 吐魯番 許昌 貴州 常州

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《7分鐘讀懂Go的臨時(shí)對(duì)象池pool以及其應(yīng)用場(chǎng)景》,本文關(guān)鍵詞  7分鐘,讀懂,的,臨時(shí),對(duì)象,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《7分鐘讀懂Go的臨時(shí)對(duì)象池pool以及其應(yīng)用場(chǎng)景》相關(guān)的同類信息!
  • 本頁(yè)收集關(guān)于7分鐘讀懂Go的臨時(shí)對(duì)象池pool以及其應(yīng)用場(chǎng)景的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章