Golang 笔记:临时对象池sync.Pool
2015-02-03
sync.Pool可以被看做是存放可被重用的值的容器,这个容器具有以下特性:可自动伸缩、高效、并发安全。
因为它的使用场景并不适用于诸如数据库连接池
这类需要我们自己管理生命周期的资源对象的池化场景,所以一般把sync.Pool
称为临时对象池(其实叫做临时对象缓存更合适),主要用来存放已经分配的但是暂时不需要使用的对象,在需要使用的时候再从临时对象池中取出。
1.sync.Pool的使用 #
1pool := &sync.Pool{
2 New: func() interface{} {
3 return 1
4 },
5}
6v := pool.Get().(int)
7fmt.Println(v)
8pool.Put(2)
9v1, _ := pool.Get().(int)
10fmt.Println(v1)
11fmt.Println(pool.Get())
sync.Pool
需要一个公有字段New
,这个字段是一个函数类型,当临时对象池中无可用的对象值时会被调用,用来创建对象值。sync.Pool
有一个Get()
方法,用于从临时对象池中获取一个对象值,如果Get方法返回的对象值是池中,在返回时会把这个对象值从池中删除sync.Pool
还有一个Put()
方法,用于将一个对象值放到临时对象池中
2.深入sync.Pool #
通过sync.Pool的源码https://golang.org/src/sync/pool.go可以看出:
1// A Pool must not be copied after first use.
2type Pool struct {
3 noCopy noCopy
4
5 local unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
6 localSize uintptr // size of the local array
7
8 // New optionally specifies a function to generate
9 // a value when Get would otherwise return nil.
10 // It may not be changed concurrently with calls to Get.
11 New func() interface{}
12}
13......
为了实现多个goroutine操作同一个sync.Pool时的高效性,sync.Pool为每个P(Processor)各分配一个本地池,当调用Get或Put方法时,会将从当前goroutine关联P的本地池中获取或存放对象。 每个P的本地池范围私有对象和共享列表对象,私有对象只有其所属的P能访问,共享列表可以被任务P访问。因为从Go语言的并发调度模型可知,同一时刻一个P只能执行一个goroutine,所以一个P的私有对象是协程安全的,访问时不需要加锁,而共享列表可以被多个P访问,是协程不安全的,所以访问时需要加锁。
2.1 获取对象 #
当我们从sync.Pool中获取对象时,会首先从当前goroutine的P的本地池的私有对象获取,如果私有对象不存在则会从当前P的共享列中获取,如果当前P的共享列表也是空的,则就会从其他P的共享列表获取。如果所有P的本地池都是空的,则直接使用New
字段指定的函数创建一个新的对象返回。
2.2 放回对象 #
当我们往sync.Pool中放置对象时,如果当前P的私有对象不存在,则将其保存为当前P的私有对象,如果当前P的私有对象已经存在,则将其放置到当前P的共享列表中。
2.3 sync.Pool和GC #
每次GC会清除掉sync.Pool中缓存的对象,因此对象缓存的有效期是到下一次GC之前。
1pool := &sync.Pool{
2 New: func() interface{} {
3 return 1
4 },
5}
6v := pool.Get().(int)
7fmt.Println(v)
8pool.Put(2)
9runtime.GC()
10v1, _ := pool.Get().(int)
11fmt.Println(v1)
3.总结 #
sync.Pool适用于存放已经分配的但是暂时不需要使用的对象,降低了复杂对象的创建和GC代价。 sync.Pool本身是协程安全的,访问时由锁开销。每一次Get操作最少0次加锁,最大NMAXPROCS次加锁,每一次Put操作最少0次加锁,最多1次加锁。sync.Pool中的对象受GC影响,因此sync.Pool不适用于连接池的场景。