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