Golang-sync.Pool使用及原理


Pool

​ 你想看一本书,需要的时候开始印刷,看完了就卖废品,1000个人想看就得印刷1000本书,卖1000本废品。这明显太不绿色了,你想了想如果建一个图书馆,里面放几本书,大家想看的时候去借,看完了再还回去,这明显高效了不少。

​ 程序员发现了你用图书馆的方式合理的利用了资源,于是照猫画虎发明了连接池、线程池、协程池、内存池,各种各样的池都是想解决同类的问题:创建连接、线程等都相对比较消耗资源,通过池存一写已经新建的连接,线程…需要的时候拿去用,不用了就再还回来。借与还的模式,节省了不少资源,大家都说好。

​ Golang的sync.Pool,对象池。采用对象池来创建对象,增加对象的重复利用率,使用的时候就不必在堆上重新创建对象可以节省开销。

使用

sync.Pool对外提供三个方法:New,Get,Put。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var pool *sync.Pool

type Book struct {
Name string
}

func init() {
pool = &sync.Pool{
New: func() interface{} {
return new(Book)
},
}
}

func main() {
book := pool.Get().(*Book)
fmt.Println(book) // &{}
book.Name = "Go Programming"
pool.Put(book)
fmt.Println(pool.Get().(*Book)) // &{Go Programming}
fmt.Println(pool.Get().(*Book)) // &{}
}

源码分析

  • 结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
type Pool struct {
noCopy noCopy // 禁止拷贝

local unsafe.Pointer // [P]poolLocal poolLocal数组指针
localSize uintptr // 数组大小

victim unsafe.Pointer // GC处理并不直接将allPools的object直接进行GC处理,而是保存到oldPools,等到下一个GC周期到了再处理。
victimSize uintptr

// 当pool没有缓存对象的时候,会调用New方法生成一个新的对象
New func() interface{}
}

type poolLocal struct {
poolLocalInternal
pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}
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 poolChain struct {
// head is the poolDequeue to push to. This is only accessed
// by the producer, so doesn't need to be synchronized.
head *poolChainElt

// tail is the poolDequeue to popTail from. This is accessed
// by consumers, so reads and writes must be atomic.
tail *poolChainElt
}


  • Get
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
func (p *Pool) Get() interface{} {
if race.Enabled { // 不允许竞态检测
race.Disable()
}
l, pid := p.pin() // 获取一个poolLocal
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 { // 没拿到 new一个
x = p.New()
}
return x
}

func (p *Pool) pin() *poolLocal {
pid := runtime_procPin()
s := atomic.LoadUintptr(&p.localSize) // load-acquire
l := p.local // load-consume
if uintptr(pid) < s {
return indexLocal(l, pid)
}
return p.pinSlow()
}

  目录