Go 是一個自動垃圾回收的編程語言,它的算法我們后續(xù)會講到,主要就是采用三色并發(fā)標(biāo)記算法標(biāo)記對象并回收。我們可以不用考慮為golang來節(jié)省什么,但是我們?nèi)绻雽⒊绦蜃龅絻?yōu)秀我們就不得不考慮減少它gc的次數(shù)窍荧,畢竟,Go 的自動垃圾回收機(jī)制還是有一個 STW(stop-the-world恨憎,程序暫停)的時間蕊退,而且,大量地創(chuàng)建在堆上的對象憔恳,也會影響垃圾回收標(biāo)記的時間
所以瓤荔,一般我們做性能優(yōu)化的時候,會采用對象池的方式钥组,把不用的對象回收起來输硝,避免被垃圾回收掉,這樣使用的時候就不必在堆上重新創(chuàng)建了
按照慣例我們先看官方文檔
官方文檔
// A Pool is a set of temporary objects that may be individually saved and
// retrieved.
//
// Any item stored in the Pool may be removed automatically at any time without
// notification. If the Pool holds the only reference when this happens, the
// item might be deallocated.
//
// A Pool is safe for use by multiple goroutines simultaneously.
//
// Pool's purpose is to cache allocated but unused items for later reuse,
// relieving pressure on the garbage collector. That is, it makes it easy to
// build efficient, thread-safe free lists. However, it is not suitable for all
// free lists.
//
// An appropriate use of a Pool is to manage a group of temporary items
// silently shared among and potentially reused by concurrent independent
// clients of a package. Pool provides a way to amortize allocation overhead
// across many clients.
//
// An example of good use of a Pool is in the fmt package, which maintains a
// dynamically-sized store of temporary output buffers. The store scales under
// load (when many goroutines are actively printing) and shrinks when
// quiescent.
//
// On the other hand, a free list maintained as part of a short-lived object is
// not a suitable use for a Pool, since the overhead does not amortize well in
// that scenario. It is more efficient to have such objects implement their own
// free list.
//
// A Pool must not be copied after first use.
//池是一組臨時對象程梦,可以分別保存和
//檢索到点把。
//
//池中存儲的任何項目都可以隨時自動刪除,而無需
//通知屿附。如果發(fā)生這種情況時郎逃,池中只有唯一的引用,則
//可能已釋放項目挺份。
//
//一個Pool可以安全地同時被多個goroutine使用褒翰。
//
//池的目的是緩存已分配但未使用的項目,以供以后重用压恒,
//減輕垃圾收集器的壓力影暴。也就是說错邦,它很容易
//建立有效的探赫,線程安全的空閑列表。但是撬呢,它并不適合所有人
//免費(fèi)列表伦吠。
//
//池的適當(dāng)用法是管理一組臨時項
//在并發(fā)的獨立服務(wù)器之間靜默共享并有可能被重用
//包的客戶。池提供了一種攤銷分配開銷的方法
//在許多客戶中魂拦。
//
// fmt包中有一個很好使用Pool的示例毛仪,它維護(hù)了一個
//動態(tài)大小的臨時輸出緩沖區(qū)存儲。店鋪規(guī)模在
//加載(當(dāng)許多goroutine正在活動打印時)芯勘,并在收縮時收縮
//靜止箱靴。
//
//另一方面,作為短期對象的一部分維護(hù)的空閑列表是
//不適合作為Pool的用途荷愕,因為間接費(fèi)用無法在
//這種情況衡怀。使此類對象實現(xiàn)自己的效率更高
//空閑列表棍矛。
//
//池在第一次使用后不得復(fù)制。
具體使用
sync.Pool 數(shù)據(jù)類型用來保存一組可獨立訪問的臨時對象抛杨,為什么說是臨時够委,因為可能隨時被移除掉,在stw的時候也有可能被移除掉怖现。所以我們不要使用這個做長鏈接保存
pool是一個線程安全的對象茁帽,池中的東西隨時可能被銷毀
New
Pool struct 包含一個 New 字段,這個字段的類型是函數(shù) func() interface{}屈嗤。當(dāng)調(diào)用 Pool 的 Get 方法從池中獲取元素潘拨,沒有更多的空閑元素可返回時,就會調(diào)用這個 New 方法來創(chuàng)建新的元素饶号。如果你沒有設(shè)置 New 字段战秋,沒有更多的空閑元素可返回時,Get 方法將返回 nil讨韭,表明當(dāng)前沒有可用的元素
Put
這個方法用于將一個元素返還給 Pool脂信,Pool 會把這個元素保存到池中,并且可以復(fù)用透硝。但如果 Put 一個 nil 值狰闪,Pool 就會忽略這個值。
Get
如果調(diào)用這個方法濒生,就會從 Pool取走一個元素埋泵,這也就意味著,這個元素會從 Pool 中移除罪治,返回給調(diào)用者丽声。不過,除了返回值是正常實例化的元素觉义,Get 方法的返回值還可能會是一個 nil(Pool.New 字段沒有設(shè)置雁社,又沒有空閑元素可以返回),所以你在使用的時候晒骇,可能需要判斷霉撵。
sync.pool源碼解析
這個是池的結(jié)構(gòu)
type Pool struct {
noCopy noCopy //這個好像是為了使用govet 可以檢測沖突
local unsafe.Pointer // 這是一個本地的環(huán)的指針
localSize uintptr // 本地數(shù)組大小
victim unsafe.Pointer // local from previous cycle
victimSize uintptr // size of victims 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{}
}
在這段代碼中,你需要關(guān)注一下 local 字段洪囤,因為所有當(dāng)前主要的空閑可用的元素都存放在 local 字段中徒坡,請求元素時也是優(yōu)先從 local 字段中查找可用的元素。local 字段包含一個 poolLocalInternal 字段瘤缩,并提供 CPU 緩存對齊喇完,從而避免 false sharing。
// Local per-P Pool appendix.
type poolLocalInternal struct {
private interface{} // Can be used only by the respective P.
shared poolChain // Local P can pushHead/popHead; any P can popTail.
}
type poolLocal struct {
poolLocalInternal
// Prevents false sharing on widespread platforms with
// 128 mod (cache line size) = 0 .
pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}
而 poolLocalInternal 也包含兩個字段:private 和 shared剥啤。private锦溪,代表一個緩存的元素奄喂,而且只能由相應(yīng)的一個 P 存取。因為一個 P 同時只能執(zhí)行一個 goroutine海洼,所以不會有并發(fā)的問題跨新。shared,可以由任意的 P 訪問坏逢,但是只有本地的 P 才能 pushHead/popHead域帐,其它 P 可以 popTail,相當(dāng)于只有一個本地的 P 作為生產(chǎn)者(Producer)是整,多個 P 作為消費(fèi)者(Consumer)肖揣,它是使用一個 local-free 的 queue 列表實現(xiàn)的。
其實首先我們得明確幾個點存儲數(shù)據(jù)指針是local
當(dāng)發(fā)生GC的時候浮入,pool是怎么運(yùn)行的:實際上會調(diào)用一個函數(shù)叫做poolcleanup
1.poolCleanup方法
func poolCleanup() {
// This function is called with the world stopped, at the beginning of a garbage collection.
// It must not allocate and probably should not call any runtime functions.
// Because the world is stopped, no pool user can be in a
// pinned section (in effect, this has all Ps pinned).
// Drop victim caches from all pools.
for _, p := range oldPools {
p.victim = nil
p.victimSize = 0
}
// Move primary cache to victim cache.
for _, p := range allPools {
p.victim = p.local
p.victimSize = p.localSize
p.local = nil
p.localSize = 0
}
// The pools with non-empty primary caches now have non-empty
// victim caches and no pools have primary caches.
oldPools, allPools = allPools, nil
}
整體大概意思是:將victim的數(shù)據(jù)給清空龙优,將local池中的數(shù)據(jù)丟給victim,
2.get方法
// Get selects an arbitrary item from the Pool, removes it from the
// Pool, and returns it to the caller.
// Get may choose to ignore the pool and treat it as empty.
// Callers should not assume any relation between values passed to Put and
// the values returned by Get.
//
// If Get would otherwise return nil and p.New is non-nil, Get returns
// the result of calling p.New.
func (p *Pool) Get() interface{} {
if race.Enabled {
race.Disable()
}
l, pid := p.pin() //將goroutine 綁定到P上
x := l.private
l.private = nil
if x == nil {
// Try to pop the head of the local shard. We prefer
// the head over the tail for temporal locality of
// reuse.
x, _ = l.shared.popHead()
if x == nil {
x = p.getSlow(pid)
}
}
runtime_procUnpin()
if race.Enabled {
race.Enable()
if x != nil {
race.Acquire(poolRaceAddr(x))
}
}
if x == nil && p.New != nil {
x = p.New()
}
return x
}
其實就四種可能
1.從本地的private取出來x
2.當(dāng)?shù)谝徊饺〔怀龅臅r候,從本地的分片頭部取一個出來
3.當(dāng)本地分片沒有了事秀,走慢方法getslow
4.當(dāng)大家都沒有了彤断,生成一個新的
3.put方法
// Put adds x to the pool.
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
}
if x != nil {
l.shared.pushHead(x)
}
runtime_procUnpin()
if race.Enabled {
race.Enable()
}
}
這個要比get方法簡單一些
1.如果放入的東西是nil ,return
2.如果private 易迹!=nil 宰衙,直接放到private中
3.從頭部放入本地分片中
3.getslow方法
func (p *Pool) getSlow(pid int) interface{} {
// See the comment in pin regarding ordering of the loads.
size := atomic.LoadUintptr(&p.localSize) // load-acquire
locals := p.local // load-consume
// Try to steal one element from other procs.
for i := 0; i < int(size); i++ {
l := indexLocal(locals, (pid+i+1)%int(size))
if x, _ := l.shared.popTail(); x != nil {
return x
}
}
// Try the victim cache. We do this after attempting to steal
// from all primary caches because we want objects in the
// victim cache to age out if at all possible.
size = atomic.LoadUintptr(&p.victimSize)
if uintptr(pid) >= size {
return nil
}
locals = p.victim
l := indexLocal(locals, pid)
if x := l.private; x != nil {
l.private = nil
return x
}
for i := 0; i < int(size); i++ {
l := indexLocal(locals, (pid+i)%int(size))
if x, _ := l.shared.popTail(); x != nil {
return x
}
}
// Mark the victim cache as empty for future gets don't bother
// with it.
atomic.StoreUintptr(&p.victimSize, 0)
return nil
}
這里其實是一個內(nèi)置函數(shù),我就不做過多講解睹欲,其實就是:當(dāng)本地分片沒有了資源了后供炼,嘗試去竊取一個資源,竊取不到的時候我們窘疮,就會從victim的內(nèi)容中去獲取一個對象袋哼,也就是垃圾分揀站獲取一個舊的對象,但是他們的注釋其實還是很有意思的:
// Try the victim cache. We do this after attempting to steal
// from all primary caches because we want objects in the
// victim cache to age out if at all possible.
我們想盡可能的不去用這個·victim對象闸衫,至于原因我目前還不太了解涛贯。
踩坑點
1.內(nèi)存泄露
var buffers = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func GetBuffer() *bytes.Buffer {
return buffers.Get().(*bytes.Buffer)
}
func PutBuffer(buf *bytes.Buffer) {
buf.Reset()
buffers.Put(buf)
}
這段代碼其實看上去人畜無害,但是實際上在我們應(yīng)用中楚堤,buf底層是一個包含切片的結(jié)構(gòu)體疫蔓,當(dāng)我們將這個切片擴(kuò)展到一定長度后歸還,僅僅是將len重置了身冬,實際上的cap會保留,那么我們知道切片的底層就是array岔乔,那么這個內(nèi)存我們一直回收不了就造成了泄露酥筝。
2.內(nèi)存浪費(fèi)
除了內(nèi)存泄漏以外,還有一種浪費(fèi)的情況雏门,就是池子中的 buffer 都比較大嘿歌,但在實際使用的時候掸掏,很多時候只需要一個小的 buffer,這也是一種浪費(fèi)現(xiàn)象宙帝。其實丧凤,我們可以將 buffer 池分成幾層。首先步脓,小于 512 byte 的元素的 buffer 占一個池子愿待;其次,小于 1K byte 大小的元素占一個池子靴患;再次仍侥,小于 4K byte 大小的元素占一個池子。這樣分成幾個池子以后鸳君,就可以根據(jù)需要农渊,到所需大小的池子中獲取 buffer 了。