共计 3696 个字符,预计需要花费 10 分钟才能阅读完成。
hi, 大家好,我是 haohongfan。
sync.Pool 应该是 Go 外面明星级别的数据结构,有很多优良的文章都在介绍这个构造,本篇文章简略分析下 sync.Pool。不过说实话 sync.Pool 并不是咱们日常开发中应用频率很高的的并发原语。
只管用的频率很低,然而不可否认的是 sync.Pool 的确是 Go 的杀手锏,正当应用 sync.Pool 会让咱们的程序性能飙升。本篇文章会从应用形式,源码分析,使用场景等方面,让你对 sync.Pool 有一个清晰的认知。
应用形式
sync.Pool 应用很简略,然而想用对却很麻烦,因为你有可能看到网上一堆谬误的示例,各位同学在搜寻 sync.Pool 的应用例子时,要特地留神。
sync.Pool 是一个内存池。通常内存池是用来避免内存泄露的(例如 C /C++)。sync.Pool 这个内存池却不是干这个的,带 GC 性能的语言都存在垃圾回收 STW 问题,须要回收的内存块越多,STW 持续时间就越长。如果能让 new 进去的变量,始终不被回收,失去反复利用,是不是就加重了 GC 的压力。
正确的应用示例(上面的 demo 选自 gin)
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset()
engine.handleHTTPRequest(c)
engine.pool.Put(c)
}
肯定要留神的是:是先 Get 获取内存空间,基于这个内存做相干的解决,而后再将这个内存还回(Put)到 sync.Pool。
Pool 构造
源码图解
简略点能够总结成上面的流程:
Sync.Pool 梳理
Pool 的内容会清理?清理会造成数据失落吗?
sync.Pool 会在每个 GC 周期内定期清理 sync.Pool 内的数据。定时清理数据并不会造成数据失落。
要分几个方面来说这个问题。
- 曾经从 sync.Pool Get 的值,在 poolClean 时虽说将 pool.local 置成了 nil,Get 到的值仍然是无效的,是被 GC 标记为彩色的,不会被 GC 回收,当 Put 后又重新加入到 sync.Pool 中
-
在第一个 GC 周期内 Put 到 sync.Pool 的数值,在第二个 GC 周期没有被 Get 应用,就会被放在 local.victim 中。如果在 第三个 GC 周期依然没有被应用就会被 GC 回收。
runtime.GOMAXPROCS 与 pool 之间的关系?
s := p.localSize l := p.local if uintptr(pid) < s {return indexLocal(l, pid), pid } if p.local == nil {allPools = append(allPools, p) } // If GOMAXPROCS changes between GCs, we re-allocate the array and lose the old one. size := runtime.GOMAXPROCS(0) local := make([]poolLocal, size) atomic.StorePointer(&p.local, unsafe.Pointer(&local[0])) // store-release runtime_StoreReluintptr(&p.localSize, uintptr(size)) // store-release
runtime.GOMAXPROCS(0) 是获取以后最大的 p 的数量。sync.Pool 的 poolLocal 数量受 p 的数量影响,会开拓 runtime.GOMAXPROCS(0) 个 poolLocal。某些场景下咱们会应用 runtime.GOMAXPROCS(N) 来扭转 p 的数量,会使 sync.Pool 的 pool.poolLocal 开释从新开拓新的空间。
为什么要开拓 runtime.GOMAXPROCS 个 local?
pool.local 是个 poolLocal 构造,这个构造体是 private + shared 链表组成,在多 goroutine 的 Get/Put 下是有数据竞争的,如果只有一个 local 就须要加锁来操作。每个 p 的 local 就能缩小加锁造成的数据竞争问题。
New() 的作用?如果没有 New 会呈现什么状况?
从下面的 pool.Get 流程图能够看进去,从 sync.Pool 获取一个内存会尝试从以后 private,shared,其余的 p 的 shared 获取或者 victim 获取,如果切实获取不到时,才会调用 New 函数来获取。也就是 New() 函数才是真正开拓内存空间的。New() 开辟出来的的内存空间应用结束后,调用 pool.Put 函数放入到 sync.Pool 中被反复利用。
如果 New 函数没有被初始化会怎么呢?很显著,sync.Pool 就废掉了,因为没有了初始化内存的中央了。
先 Put,再 Get 会呈现什么状况?
肯定要留神,上面这个例子的用法是谬误的
func main(){
pool:= sync.Pool{New: func() interface{} {return item{}
},
}
pool.Put(item{value:1})
data := pool.Get()
fmt.Println(data)
}
如果你间接跑这个例子,能失去你想像的后果,然而在某些状况下就不是这个后果了。
在 Pool.Get 正文外面有这么一句话:“Callers should not assume any relation between values passed to Put and the values returned by Get.”,通知咱们不能把值 Pool.Put 到 sync.Pool 中,再应用 Pool.Get 取出来,因为 sync.Pool 不是 map 或者 slice,放入的值是有可能拿不到的,sync.Pool 的数据结构就不反对做这个事件。
后面说应用 sync.Pool 容易被谬误示例误导,就是下面这个写法。为什么 Put 的值 再 Get 会呈现问题?
- 状况 1:sync.Pool 的 poolCleanup 函数在零碎 GC 时会被调用,Put 到 sync.Pool 的值,因为有可能始终得不到利用,被在某个 GC 周期内就有可能被开释掉了。
- 状况 2:不同的 goroutine 绑定的 p 有可能是不一样的,以后 p 对应的 goroutine 放入到 sync.Pool 的值有可能被其余的 p 对应的 goroutine 取到,导致以后 goroutine 再也取不到这个值。
- 状况 3:应用 runtime.GOMAXPROCS(N) 来扭转 p 的数量,会使 sync.Pool 的 pool.poolLocal 开释从新开拓新的空间,导致 sync.Pool 被开释掉。
- 状况 4:还有很多状况
只 Get 不 Put 会内存泄露吗?
应用其余的池,如连接池,如果取连贯应用后不放回连接池,就会呈现连接池泄露, 是不是 sync.Pool 也有这个问题呢?
通过下面的流程图,能够看进去 Pool.Get 的时候会尝试从以后 private,shared,其余的 p 的 shared 获取或者 victim 获取,如果切实获取不到时,才会调用 New 函数来获取,New 进去的内容自身还是受零碎 GC 来管制的。所以如果咱们提供的 New 实现不存在内存泄露的话,那么 sync.Pool 是不会内存泄露的。当 New 进去的变量如果不再被应用,就会被零碎 GC 给回收掉。
如果不 Put 回 sync.Pool,会造成 Get 的时候每次都调用的 New 来从堆栈申请空间,达不到加重 GC 压力。
应用场景
下面说到 sync.Pool 业务开发中不是一个罕用构造,咱们业务开发中没必要假想某块代码会有强烈的性能问题,一上来就用 sync.Pool 硬怼。sync.Pool 次要是为了解决 Go GC 压力过大问题的,所以个别状况下,当线上高并发业务呈现 GC 问题须要被优化时,才须要用 sync.Pool 出场。
应用留神点
- sync.Pool 同样不能被复制。
- 好的应用习惯,从 pool.Get 进去的值进行数据的清空(reset),避免垃圾数据净化。
本文基于的 Go 源码版本:1.16.2
参考链接
- 深度解密 Go 语言之 sync.Pool https://www.cnblogs.com/qcrao-2018/p/12736031.html
- 请问 sync.Pool 有什么毛病?https://mp.weixin.qq.com/s/2ZC1BWTylIZMmuQ3HwrnUg
- Go 1.13 中 sync.Pool 是如何优化的? https://colobu.com/2019/10/08/how-is-sync-Pool-improved-in-Go-1-13/
sync.Pool 的分析到这里根本就写完了,想跟我交换的能够在评论区留言。
sync.Pool 残缺流程图获取链接:链接: https://pan.baidu.com/s/1T5e8… 明码: ngea 其余模块流程图,请关注公众号 HHFCodeRv 回复 1 获取。
学习材料分享,关注公众号回复指令:
- 回复 0,获取《Go 面经》
- 回复 1,获取《Go 源码流程图》