golang sync .pool

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.

引用:/src/sync/pool.go:44

//池是一組臨時對象程梦,可以分別保存和
//檢索到点把。
//
//池中存儲的任何項目都可以隨時自動刪除,而無需
//通知屿附。如果發(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 了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末或颊,一起剝皮案震驚了整個濱河市砸紊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌囱挑,老刑警劉巖批糟,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異看铆,居然都是意外死亡徽鼎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進(jìn)店門弹惦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來否淤,“玉大人,你說我怎么就攤上這事棠隐∈眨” “怎么了?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵助泽,是天一觀的道長啰扛。 經(jīng)常有香客問我,道長嗡贺,這世上最難降的妖魔是什么隐解? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮诫睬,結(jié)果婚禮上煞茫,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好续徽,可當(dāng)我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布蚓曼。 她就那樣靜靜地躺著,像睡著了一般钦扭。 火紅的嫁衣襯著肌膚如雪纫版。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天客情,我揣著相機(jī)與錄音其弊,去河邊找鬼。 笑死裹匙,一個胖子當(dāng)著我的面吹牛瑞凑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播概页,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼籽御,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了惰匙?” 一聲冷哼從身側(cè)響起技掏,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎项鬼,沒想到半個月后哑梳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡绘盟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年鸠真,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片龄毡。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡吠卷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沦零,到底是詐尸還是另有隱情祭隔,我是刑警寧澤,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布路操,位于F島的核電站疾渴,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏屯仗。R本人自食惡果不足惜搞坝,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望祭钉。 院中可真熱鬧瞄沙,春花似錦、人聲如沸慌核。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽垮卓。三九已至垫桂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間粟按,已是汗流浹背诬滩。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留灭将,地道東北人疼鸟。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像庙曙,于是被迫代替她去往敵國和親空镜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,554評論 2 349

推薦閱讀更多精彩內(nèi)容