一.sync.Pool定義
我們通常用golang來構建高併發場景下的應用,但是由於golang內建的GC機制會影響應用的性能,爲了減少GC,golang提供了對象重用的機制,也就是sync.Pool對象池。 sync.Pool是可伸縮的,併發安全的。其大小僅受限於內存的大小,可以被看作是一個存放可重用對象的值的容器。 設計的目的是存放已經分配的但是暫時不用的對象,在需要用到的時候直接從pool中取。
官方的解釋:臨時對象池是一些可以分別存儲和取出的臨時對象,池中的對象會在沒有任何通知的情況下移出(釋放或重新使用)。pool在多協程的環境下是安全的,在fmt包中有一個使用pool的例子,它維護了一個動態大小的輸出buffer。另外,一些短生命週期的對象不適合使用pool來維護,這種情況使用go自己的free list更高效。
二.sync.Pool的實現
2.1使對象池高效
爲了使多個goroutine操作同一個pool做到高效,sync.pool爲每一個p都分配了一個子池。當執行get或者put操作時,會對當前goroutine掛載的子池操作。每個子池都有一個私有對象和共享列表對象,私有對象只有對應的p能夠訪問,因爲同一個p同一時間只能操作執行一個goroutine,因此對私有對象的操作不需要加鎖;但共享列表是和其他P分享的,因此操作是需要加鎖的。
獲取對象的過程:
- 固定某個P,嘗試從私有對象中獲取, 如果是私有對象則返回該對象,並把私有對象賦空。
- 如果私有對象是空的,需要加鎖,從當前固定的p的共享池中獲取-並從該共享隊列中刪除這個對象。
- 如果當前的子池都是空的,嘗試去其他P的子池的共享列表偷取一個,如果用戶沒有註冊New函數則返回nil。
歸還對象的過程:
- 固定到某個p,如果私有對象爲空則放到私有對象。
- 如果私有對象不爲空,加鎖,加入到該P子池的共享列表中。
2.2 對象池的詳細實現
2.2.1 對象池結構
type Pool struct {
noCopy noCopy //防止copy
local unsafe.Pointer //本地p緩存池指針
localSize uintptr //本地p緩存池大小
//當池中沒有對象時,會調用New函數調用一個對象
New func() interface{}
}
2.2.2 獲取對象池中的對象
func (p *Pool) Get() interface{} {
if race.Enabled {
race.Disable()
}
//獲取本地的poolLocal對象
l := p.pin()
//先獲取private池中的私有變量
x := l.private
l.private = nil
runtime_procUnpin()
if x == nil {
//查找本地的共享池,因爲本地的共享池可能被其他p訪問,所以要加鎖
l.Lock()
last := len(l.shared) - 1
if last >= 0 {
//如果本地共享池有對象,取走最後一個
x = l.shared[last]
l.shared = l.shared[:last]
}
l.Unlock()
//查找其他p的共享池
if x == nil {
x = p.getSlow()
}
}
if race.Enabled {
race.Enable()
if x != nil {
race.Acquire(poolRaceAddr(x))
}
}
//未找到其他可用元素,則調用New生成
if x == nil && p.New != nil {
x = p.New()
}
return x
}
從共享池中獲取可用元素:
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()
}
return x
}
2.2.3 歸還對象池中的對象
func (p *Pool) Put(x interface{}) {
if x == nil {
return
}
if race.Enabled {
if fastrand()%4 == 0 {
//1/4的概率會把該元素扔掉
return
}
race.ReleaseMerge(poolRaceAddr(x))
race.Disable()
}
l := p.pin()
if l.private == nil {
//賦值給私有變量
l.private = x
x = nil
}
runtime_procUnpin()
if x != nil {
//訪問共享池加鎖
l.Lock()
l.shared = append(l.shared, x)
l.Unlock()
}
if race.Enabled {
race.Enable()
}
}
三.sync.Pool 使用
// 一個[]byte的對象池,每個對象爲一個[]byte
var bytePool = sync.Pool{
New: func() interface{} {
b := make([]byte, 512)
return &b
},
}
func main() {
a := time.Now().Unix()
// 不使用對象池
for i := 0; i < 1000000000; i++{
obj := make([]byte,512)
_ = obj
}
b := time.Now().Unix()
// 使用對象池
for i := 0; i < 1000000000; i++{
obj := bytePool.Get().(*[]byte)
_ = obj
bytePool.Put(obj)
}
c := time.Now().Unix()
fmt.Println("without pool ", b - a, "s")
fmt.Println("with pool ", c - b, "s")
}
// without pool 17 s
// with pool 12 s
四.sync.Pool的使用場景
sync.Pool的get方法不會對獲取到的對象做任何的保證,因爲放入的本地子池中的值可能在任何是由被刪除,而且不會通知調用者。放入共享池的值有可能被其他的goroutine偷走。隨意臨時對象池適合存儲一些臨時數據,不適合用來存儲數據庫連接等持久化存儲的對象。