简析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语言学习笔记、语法知识、技术要点和个人理解及实战

全部评论

相关推荐

2024-12-25 09:09
四川师范大学 运营
想和你交朋友的潜伏者要冲国企:先去沃尔玛亲身感受标准化流程体系,一两年后再跳槽国内任何零售行业,可以有更大选择权吧?
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务