golang sync.Pool 使用和源碼分析

    golang 在寫高頻服務的時候,如何解決gc問題,對象池是一個頗有效果的方式,本文闡述下對象池的兩種使用方式,和對對象池的源碼分析,以及使用pool 的要點。golang 的對象池源碼在避免鎖競爭還利用了分段鎖的思想減小鎖的競爭,代碼比較精彩。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 便可。app

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。socket

    而後,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 一個隊列,還考慮到了均衡的狀況,是比較巧妙和值得學習的。以上。。。

相關文章
相關標籤/搜索