我所理解的Sync Pool

看gin源碼時(shí)發(fā)現(xiàn)了sync.Pool的使用

// gin.go:L144
func New() *Engine {
    ...

    engine.pool.New = func() interface{} {
        return engine.allocateContext()
    }
    return engine
}

// gin.go: L346
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    c := engine.pool.Get().(*Context)
    c.writermem.reset(w)
    c.Request = req
    c.reset()

    engine.handleHTTPRequest(c)

    engine.pool.Put(c)
}

那個(gè)時(shí)候其實(shí)不太明白這個(gè)Pool是在干啥用, 大致覺得應(yīng)該是內(nèi)存池之類的. 后面想仔細(xì)看下sync.Pool具體怎么用, 我就去直接看了下Pool的源碼, 然后直接懵逼了

因?yàn)榛究床欢溥壿? 因?yàn)镻ool的源碼涉及到Golang的調(diào)度相關(guān)的知識(shí). 要是不明白Golang是如何調(diào)度的, 基本是看不懂Pool的源碼的(雖說它只有短短的259行代碼, 文件路徑: SDK/src/sync/pool.go). 這里推薦Go調(diào)度器系列系列文章

后面我就去看看別人是怎么理解和使用Pool的, 我就搜到了這么一篇文章 go語言的官方包sync.Pool的實(shí)現(xiàn)原理和適用場(chǎng)景里面有這么一個(gè)例子

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)
}

以及Golang中國論壇上一個(gè)人的提問, 我不禁陷入了深深的疑問, 這個(gè)Golang的Pool到底是用來做什么的?

我疑問的地方:

  1. sync.Pool的運(yùn)用場(chǎng)景是什么, 哪些地方能用到Pool, 哪些地方不能用?
  2. 為啥CSDN上先Put后Get, 就能拿到1; 而Golang中國上那個(gè)提問, 為啥得到的順序就不確定了?

我后面想了很久, 以及再回頭去看gin, logrus的源碼, 我才想明白: 這兩個(gè)例子都是"坑貨". 這兩個(gè)例子都是sync.Pool使用的反面例子, 都是不正確的用法. 其實(shí)這些在源碼的注釋中, 都是由明確說明的, 只是當(dāng)時(shí)沒能理解. Callers should not assume any relation between values passed to Put and the values returned by Get. 簡單來說: 就是GetPut沒有任何關(guān)系, 我們用Pool的時(shí)候, 要時(shí)刻記得這個(gè).

因此, 我的疑問2就迎刃而解了, 其實(shí)就是這個(gè)一直困惑著我

關(guān)于疑問1就更簡單了

// 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.

簡單來說, Pool就是為了減少GC壓力的, 重復(fù)利用內(nèi)存. 千萬不能把他當(dāng)成內(nèi)存池使用

其實(shí)Pool的用法很簡單, 就是先Get, 用完之后Put, 如gin的使用.

再比如logrus的用法

func (logger *Logger) Println(args ...interface{}) {
    entry := logger.newEntry()
    entry.Println(args...)
    logger.releaseEntry(entry)
}

func (logger *Logger) newEntry() *Entry {
    entry, ok := logger.entryPool.Get().(*Entry)
    if ok {
        return entry
    }
    return NewEntry(logger)
}

func (logger *Logger) releaseEntry(entry *Entry) {
    entry.Data = map[string]interface{}{}
    logger.entryPool.Put(entry)
}

所以別被別的池子帶跑了, golang里的sync.Pool就是GC優(yōu)化的, 用法很簡單

gocn有這么一個(gè)問題: 想在pool的基礎(chǔ)上做一個(gè)限制池中對(duì)象數(shù)量的功能, 發(fā)現(xiàn)還是多次執(zhí)行pool.New. 期望是只執(zhí)行一次NewBuffer,也就是只打印一次alloc。 實(shí)際上每次執(zhí)行,會(huì)打印多次alloc

const MaxFrameSize = 5000

func main() {
    for i := 0; i < 10; i++ {
        // 多個(gè)協(xié)程想從pool中拿到對(duì)象
        go func() {
            c := getBuf()
            putBuf(c)
            log.Println("put done")
        }()
    }

    time.Sleep(3 * time.Second)
}

var bufPool = sync.Pool{
    New: func() interface{} {
        log.Println("alloc")
        return bytes.NewBuffer(make([]byte, 0, MaxFrameSize))
    },
}

var bufPoolChan = make(chan bool, 1)

func getBuf() *bytes.Buffer {
    bufPoolChan <- true
    b := bufPool.Get().(*bytes.Buffer)
    b.Reset()
    return b
}

func putBuf(b *bytes.Buffer) {
    bufPool.Put(b)
    <-bufPoolChan
}

大神解答:

sync.Pool的源代碼里說了,pool里的對(duì)象隨時(shí)都有可能被自動(dòng)移除,并且沒有任何通知。sync.Pool的數(shù)量是不可控制的。

Pool調(diào)用New與線程調(diào)度有關(guān),Pool內(nèi)部有一個(gè)localPool的數(shù)組,每個(gè)P對(duì)應(yīng)其中一個(gè)localPool,在當(dāng)前P執(zhí)行g(shù)oroutine的時(shí)候,優(yōu)先從當(dāng)前的localPool的private變量取,娶不到在從shared列表里面取,再取不到就嘗試從別的P的localPool的shared里面偷一個(gè)。最后實(shí)在取不到就New一個(gè)。

由于你的bufPoolChan限制基本上10個(gè)goroutine就在兩個(gè)P后面排隊(duì)輪流執(zhí)行,所以alloc就會(huì)出現(xiàn)兩次,后面的基本就是從這兩個(gè)localPool的private取出來的。

如果取消這個(gè)限制,10個(gè)goroutine很快就被分配到10個(gè)P上去了,對(duì)應(yīng)就有10個(gè)localPool,10次每次取private都取不到,取shared列表也取不到,別的localPool也沒得偷,就會(huì)New10次,alloc就會(huì)出現(xiàn)10次。

其他高級(jí)用法, 后面再補(bǔ)充

參考文章

[譯] CockroachDB GC優(yōu)化總結(jié)
Golang 優(yōu)化之路——臨時(shí)對(duì)象池
用Benchmark驗(yàn)證sync.Pool對(duì)GC latency的優(yōu)化效果
Go語言實(shí)戰(zhàn)筆記(十六)| Go 并發(fā)示例-Pool
Pool

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容