golang 在寫高頻服務的時候,如何解決gc問題,對象池是一個頗有效果的方式,本文闡述下對象池的兩種使用方式,和對對象池的源碼分析,以及使用pool 的要點。golang 的對象池源碼在避免鎖競爭還利用了分段鎖的思想減小鎖的競爭,代碼比較精彩。golang
該文章後續仍在不斷的更新修改中, 請移步到原文地址http://www.dmwan.cc/?p=152數組
首先sync.Pool 有兩種使用方式,使用效果沒有區別。緩存
第一種,實例化的時候,實現New 函數便可:數據結構
package main import( "fmt" "sync" ) func main() { p := &sync.Pool{ New: func() interface{} { return 0 }, } a := p.Get().(int) p.Put(1) b := p.Get().(int) fmt.Println(a, b) }
第二種,get 取值的時候,判斷是否爲nil 便可。app
package main import( "fmt" "sync" ) func main() { p := &sync.Pool{} a := p.Get() if a == nil { a = func() interface{} { return 0 } } p.Put(1) b := p.Get().(int) fmt.Println(a, b) }
這兩種實現方式,最後的效果是同樣的,也反應了pool 的特性,get 返回值是new 的對象,或者nil。socket
而後,pool 底層究竟是怎樣的數據結構?就是一個metux 和 slice?其實也是相似,只是加了些其餘特性而已,下面數據結構:函數
type Pool struct { local unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal localSize uintptr // size of the local array // New optionally specifies a function to generate // a value when Get would otherwise return nil. // It may not be changed concurrently with calls to Get. New func() interface{} } // Local per-P Pool appendix. type poolLocal struct { private interface{} // Can be used only by the respective P. shared []interface{} // Can be used by any P. Mutex // Protects shared. pad [128]byte // Prevents false sharing. }
這裏的local 是個poolLocal 的數組,localsize 是數組的大小。其中,從get 和put 方法看,爲每一個thread 維護了一個poolLocal 數據結構。不一樣線程取數據的時候,先判斷下hash 到哪一個線程去了,分別去對應的poolLocal 中去取數據,這是利用了分段鎖的思想。源碼分析
具體實現能夠看get 方法:性能
func (p *Pool) Get() interface{} { if raceenabled { if p.New != nil { return p.New() } return nil } l := p.pin() // 獲取當前線程的poolLocal,也就是p.local[pid]。 x := l.private //判斷臨時變量是否有值,有值即返回 l.private = nil runtime_procUnpin() if x != nil { return x } l.Lock() //臨時對象沒值到本地的緩存列表中去取 last := len(l.shared) - 1 if last >= 0 { x = l.shared[last] l.shared = l.shared[:last] } l.Unlock() if x != nil { return x } return p.getSlow() //當本線程的緩存對象已經沒有,去其餘線程緩存列表中取 }
這裏代碼的註釋比較詳盡了,原本維護一個mutex ,如今變成競爭多個mutex ,下降了鎖的競爭。性能天然很是好。學習
最後是getSlow 方法,從其餘線程的變量中去steal 偷。runtime 也喜歡搞這種事。。。
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)) l.Lock() last := len(l.shared) - 1 if last >= 0 { x = l.shared[last] l.shared = l.shared[:last] l.Unlock() break } l.Unlock() } if x == nil && p.New != nil { //其餘線程沒有,那麼new 一個 x = p.New() } return x }
最後,pool 還有個特性是當gc 的時候全部的緩存對象都要被清理,調用的是PoolCleanUp,沒什麼特別之處。可是這個特性要求了pool 絕對不能作有狀態的緩存,相似socket的緩存池。
這裏的分段鎖,爲每一個線程bind 一個隊列,還考慮到了均衡的狀況,是比較巧妙和值得學習的。以上。。。