我所理解的Sync Pool

看gin源码时发现了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)
}

那个时候其实不太明白这个Pool是在干啥用, 大致觉得应该是内存池之类的. 后面想仔细看下sync.Pool具体怎么用, 我就去直接看了下Pool的源码, 然后直接懵逼了

因为基本看不懂其逻辑, 因为Pool的源码涉及到Golang的调度相关的知识. 要是不明白Golang是如何调度的, 基本是看不懂Pool的源码的(虽说它只有短短的259行代码, 文件路径: SDK/src/sync/pool.go). 这里推荐Go调度器系列系列文章

后面我就去看看别人是怎么理解和使用Pool的, 我就搜到了这么一篇文章 go语言的官方包sync.Pool的实现原理和适用场景里面有这么一个例子

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中国论坛上一个人的提问, 我不禁陷入了深深的疑问, 这个Golang的Pool到底是用来做什么的?

我疑问的地方:

  1. sync.Pool的运用场景是什么, 哪些地方能用到Pool, 哪些地方不能用?
  2. 为啥CSDN上先Put后Get, 就能拿到1; 而Golang中国上那个提问, 为啥得到的顺序就不确定了?

我后面想了很久, 以及再回头去看gin, logrus的源码, 我才想明白: 这两个例子都是"坑货". 这两个例子都是sync.Pool使用的反面例子, 都是不正确的用法. 其实这些在源码的注释中, 都是由明确说明的, 只是当时没能理解. Callers should not assume any relation between values passed to Put and the values returned by Get. 简单来说: 就是GetPut没有任何关系, 我们用Pool的时候, 要时刻记得这个.

因此, 我的疑问2就迎刃而解了, 其实就是这个一直困惑着我

关于疑问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压力的, 重复利用内存. 千万不能把他当成内存池使用

其实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优化的, 用法很简单

gocn有这么一个问题: 想在pool的基础上做一个限制池中对象数量的功能, 发现还是多次执行pool.New. 期望是只执行一次NewBuffer,也就是只打印一次alloc。 实际上每次执行,会打印多次alloc

const MaxFrameSize = 5000

func main() {
    for i := 0; i < 10; i++ {
        // 多个协程想从pool中拿到对象
        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里的对象随时都有可能被自动移除,并且没有任何通知。sync.Pool的数量是不可控制的。

Pool调用New与线程调度有关,Pool内部有一个localPool的数组,每个P对应其中一个localPool,在当前P执行goroutine的时候,优先从当前的localPool的private变量取,娶不到在从shared列表里面取,再取不到就尝试从别的P的localPool的shared里面偷一个。最后实在取不到就New一个。

由于你的bufPoolChan限制基本上10个goroutine就在两个P后面排队轮流执行,所以alloc就会出现两次,后面的基本就是从这两个localPool的private取出来的。

如果取消这个限制,10个goroutine很快就被分配到10个P上去了,对应就有10个localPool,10次每次取private都取不到,取shared列表也取不到,别的localPool也没得偷,就会New10次,alloc就会出现10次。

其他高级用法, 后面再补充

参考文章

[译] CockroachDB GC优化总结
Golang 优化之路——临时对象池
用Benchmark验证sync.Pool对GC latency的优化效果
Go语言实战笔记(十六)| Go 并发示例-Pool
Pool

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容