
Go Sync.Pool
sync.Pool: 保存和复用临时对象,减少内存分配,降低 GC 压力。
介绍
Go 语言从 1.3 版本开始提供了对象重用的机制,即 sync.Pool。sync.Pool 是可伸缩的,同时也是并发安全的,其大小仅受限于内存的大小。sync.Pool 用于存储那些被分配了但是没有被使用,而未来可能会使用的值。这样就可以不用再次经过内存分配,可直接复用已有对象,减轻 GC 的压力,从而提升系统的性能。
sync.Pool 的大小是可伸缩的,高负载时会动态扩容,存放在池中的对象如果不活跃了会被自动清理。
使用
声明对象池
var pool = sync.Pool {
New: func() interface{} {
return &Student{}
},
}
Get & Put
stu := studentPool.Get().(*Student)
json.Unmarshal(buf, stu)
studentPool.Put(stu)
- Get() 用于从对象池中获取对象,因为返回值是 interface{},因此需要类型转换。
- Put() 则是在对象使用完毕后,返回对象池。
GC
对于 Pool 而言,并不能无限扩展,否则对象占用内存太多了,会引起内存溢出。Go 会在GC 发生时清空或清除部分缓存对象
在 pool.go
文件的 init 函数里,注册了 GC 发生时清理 Pool 的函数
// src/sync/pool.go
func init() {
runtime_registerPoolCleanup(poolCleanup)
}
编译器在背后做了一些动作:
// src/runtime/mgc.go
// Hooks for other packages
var poolcleanup func()
// 利用编译器标志将 sync 包中的清理注册到运行时
//go:linkname sync_runtime_registerPoolCleanup sync.runtime_registerPoolCleanup
func sync_runtime_registerPoolCleanup(f func()) {
poolcleanup = f
}
func poolCleanup() {
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
}
oldPools, allPools = allPools, nil
}
poolCleanup
会在 STW 阶段被调用。整体看起来,比较简洁。主要是将 local 和 victim 作交换,这样也就不致于让 GC 把所有的 Pool 都清空了,有 victim 在“兜底”。
总结
sync.Pool
关键思想是对象的复用,避免重复创建、销毁。将暂时不用的对象缓存起来,待下次需要的时候直接使用,不用再次经过内存分配,复用对象的内存,减轻 GC 的压力。sync.Pool
是协程安全的,使用起来非常方便。设置好 New 函数后,调用 Get 获取,调用 Put 归还对象。- 不要对 Get 得到的对象有任何假设,更好的做法是归还对象时,将对象“清空”。
- Pool 里对象的生命周期受 GC 影响,不适合于做连接池,因为连接池需要自己管理对象的生命周期。
- Pool 不可以指定⼤⼩,⼤⼩只受制于 GC 临界值。
procPin
将 G 和 P 绑定,防止 G 被抢占。在绑定期间,GC 无法清理缓存的对象。- 在加入 victim 机制前,
sync.Pool
里对象的最⼤缓存时间是一个 GC 周期,当 GC 开始时,没有被引⽤的对象都会被清理掉;加入 victim 机制后,最大缓存时间为两个 GC 周期。 Victim Cache
本来是计算机架构里面的一个概念,是 CPU 硬件处理缓存的一种技术,sync.Pool
引入的意图在于降低 GC 压力的同时提高命中率。sync.Pool
的最底层使用切片加链表来实现双端队列,并将缓存的对象存储在切片中。