golang sync.Pool 使用和源码分析
golang 在写高频服务的时候,如何解决gc问题,对象池是一个很有效果的方式,本文阐述下对象池的两种使用方式,和对对象池的源码分析,以及使用pool 的要点。golang 的对象池源码在避免锁竞争还利用了分段锁的思想减少锁的竞争,代码比较精彩。
该文章后续仍在不断的更新修改中, 请移步到原文地址http://www.dmwan.cc/?p=152
首先sync.Pool 有两种使用方式,使用效果没有区别。
第一种,实例化的时候,实现New 函数即可:
package main import( "fmt" "sync" ) func main() { p := &sync.Pool{ New: func() interface{} { return 0 }, } a := p.Get().(int) p.Put(1) b := p.Get().(int) fmt.Println(a, b) }
第二种,get 取值的时候,判断是否为nil 即可。
package main import( "fmt" "sync" ) func main() { p := &sync.Pool{} a := p.Get() if a == nil { a = func() interface{} { return 0 } } p.Put(1) b := p.Get().(int) fmt.Println(a, b) }
这两种实现方式,最后的效果是一样的,也反应了pool 的特性,get 返回值是new 的对象,或者nil。
然后,pool 底层到底是怎样的数据结构?就是一个metux 和 slice?其实也是类似,只是加了些其他特性而已,下面数据结构:
type Pool struct { local unsafe.Pointer // local fixed-size per-P pool, actual type is[p] 这里的local 是个poolLocal 的数组,localsize 是数组的大小。其中,从get 和put 方法看,为每个thread 维护了一个poolLocal 数据结构。不同线程取数据的时候,先判断下hash 到哪个线程去了,分别去对应的poolLocal 中去取数据,这是利用了分段锁的思想。poolLocal localSize uintptr // size of the local array // New optionally specifies a function to generate // a value when Get would otherwise return nil. // It may not be changed concurrently with calls to Get. New func() interface{} } // Local per-P Pool appendix. type poolLocal struct { private interface{} // Can be used only by the respective P. shared []interface{} // Can be used by any P. Mutex // Protects shared. pad [128]byte // Prevents false sharing. }
具体实现可以看get 方法:
func (p *Pool) Get() interface{} { if raceenabled { if p.New != nil { return p.New() } return nil } l := p.pin() // 获取当前线程的poolLocal,也就是p.local[pid]。 x := l.private //判断临时变量是否有值,有值即返回 l.private = nil runtime_procUnpin() if x != nil { return x } l.Lock() //临时对象没值到本地的缓存列表中去取 last := len(l.shared) - 1 if last >= 0 { x = l.shared[last] l.shared = l.shared[:last] } l.Unlock() if x != nil { return x } return p.getSlow() //当本线程的缓存对象已经没有,去其他线程缓存列表中取 }
这里代码的注释比较详尽了,本来维护一个mutex ,现在变成竞争多个mutex ,降低了锁的竞争。性能自然非常好。
最后是getSlow 方法,从其他线程的变量中去steal 偷。runtime 也喜欢搞这种事。。。
func (p *Pool) getSlow() (x interface{}) { // See the comment in pin regarding ordering of the loads. size := atomic.LoadUintptr(&p.localSize) // load-acquire local := p.local // load-consume // Try to steal one element from other procs. pid := runtime_procPin() runtime_procUnpin() for i := 0; i < int(size); i++ { //遍历其他线程的缓存队列 l := indexLocal(local, (pid+i+1)%int(size)) l.Lock() last := len(l.shared) - 1 if last >= 0 { x = l.shared[last] l.shared = l.shared[:last] l.Unlock() break } l.Unlock() } if x == nil && p.New != nil { //其他线程没有,那么new 一个 x = p.New() } return x }
最后,pool 还有个特性是当gc 的时候所有的缓存对象都要被清理,调用的是PoolCleanUp,没什么特别之处。但是这个特性要求了pool 绝对不能做有状态的缓存,类似socket的缓存池。
这里的分段锁,为每个线程bind 一个队列,还考虑到了均衡的情况,是比较巧妙和值得学习的。以上。。。
- golang sync.Pool包的使用和一些注意地方
- 不得不知道的golang之sync.Mutex互斥锁源码分析
- .NET / Rotor源码分析5 - 开始使用WinDbg+SOS调试,sscoree.dll,加载SOS并设置JIT断点
- 第二人生的源码分析(五十八)使用FreeType字体
- 第二人生的源码分析(六十六)使用Expat XML解析器的例子
- 第二人生的源码分析(五十八)使用FreeType字体
- boost.pool源码整理和使用说明
- 第二人生的源码分析(六十七)LLXMLNode使用Expat库打开文件
- 第二人生的源码分析(六十八)LLXMLNode使用Expat库分析XML文件
- 第二人生的源码分析(六十八)LLXMLNode使用Expat库分析XML文件
- 第二人生的源码分析(六十九)使用LLXmlTree类来分析XML配置文件
- 第一个使用socket的源码分析
- 蔡军生先生第二人生的源码分析(五十八)使用FreeType字体
- 第二人生的源码分析(六十六)使用Expat XML解析器的例子
- 第二人生的源码分析(四十一)使用Apache运行库线程
- .NET / Rotor源码分析5 - 开始使用WinDbg+SOS调试,sscoree.dll,加载SOS并设置JIT断点
- Apache 中使用的 APR Memory Pool 分析
- .NET / Rotor源码分析5 - 开始使用WinDbg+SOS调试,sscoree.dll,加载SOS并设置JIT断点
- 第二人生的源码分析(六十七)LLXMLNode使用Expat库打开文件
- .NET / Rotor源码分析5 - 开始使用WinDbg+SOS调试,sscoree.dll,加载SOS并设置JIT断点