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]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.  
}  

    这里的local 是个poolLocal 的数组,localsize 是数组的大小。其中,从get 和put 方法看,为每个thread 维护了一个poolLocal 数据结构。不同线程取数据的时候,先判断下hash 到哪个线程去了,分别去对应的poolLocal 中去取数据,这是利用了分段锁的思想。

    具体实现可以看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 一个队列,还考虑到了均衡的情况,是比较巧妙和值得学习的。以上。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值