简析Go语言sync.Pool
sync.Pool是一个数据对象缓存池,它具有如下特点:
①它是goroutine并发安全的,可以被多个goroutine同时使用
②放入该缓存池中的数据对象的生命是暂时的,随时都可能被垃圾回收掉
③缓存池中的数据对象是可以重复利用的,这样可以在一定程度上降低数据对象重新分配的频度,减轻GC回收压力
④sync.Pool为每一个P(goroutine调度模型中的P)单独建一个local缓存池,进一步降低高并发下对锁的争抢
通过sync.Pool来复用数据对象的方式可以有效降低内存分配频率,减轻垃圾回收的压力,从而提高处理性能。sync.Pool的一个典型的应用就是建立像bytes.Buffer这样类型的临时缓存对象池:
var bufPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
但是这么使用存在一个问题:由于sync.Pool的Get方法从缓存池中挑选bytes.Buffer数据对象时并未考虑该数据对象是否满足调用者的需求,因此一旦返回的Buffer对象是刚刚被“大数据”撑大后的,并且即将被长期用于处理一些“小数据”时,这个Buffer对象所占用的内存将长时间得不到释放。一旦出现这种情况,将会给Go应用带来沉重的内存消耗负担。为此,Go目前采用两种方案来缓解这一问题:
①限制要放回缓存池中的数据大小
②建立多级缓存池
以下时标准库的http包在处理http2数据时预先建立了多个不同大小的缓存池:
var (
http2dataChunkSizeClasses = []int{
1 << 10,
2 << 10,
4 << 10,
8 << 10,
16 << 10,
}
http2dataChunkPools = [...]sync.Pool{
{New: func() interface{} { return make([]byte, 1<<10) }},
{New: func() interface{} { return make([]byte, 2<<10) }},
{New: func() interface{} { return make([]byte, 4<<10) }},
{New: func() interface{} { return make([]byte, 8<<10) }},
{New: func() interface{} { return make([]byte, 16<<10) }},
}
)
func http2getDataBufferChunk(size int64) []byte {
i := 0
for ; i < len(http2dataChunkSizeClasses)-1; i++ {
if size <= int64(http2dataChunkSizeClasses[i]) {
break
}
}
return http2dataChunkPools[i].Get().([]byte)
}
func http2putDataBufferChunk(p []byte) {
for i, n := range http2dataChunkSizeClasses {
if len(p) == n {
http2dataChunkPools[i].Put(p)
return
}
}
panic(fmt.Sprintf("unexpected buffer len=%v", len(p)))
}
Go语言基础及实战 文章被收录于专栏
Go语言学习笔记、语法知识、技术要点和个人理解及实战
查看11道真题和解析
