Go 1.3 的sync包中加入一個新特性:Pool。官方文檔可以看這里http://golang.org/pkg/sync/#Pool
這個類設計的目的是用來保存和復用臨時對象,以減少內存分配,降低CG壓力。
type Pool
func (p *Pool) Get() interface{}
func (p *Pool) Put(x interface{})
New func() interface{}
下面說說Pool的實現:
1.定時清理
文檔上說,保存在Pool中的對象會在沒有任何通知的情況下被自動移除掉。實際上,這個清理過程是在每次垃圾回收之前做的。垃圾回收是固定兩分鍾觸發一次。而且每次清理會將Pool中的所有對象都清理掉!
2.如何管理數據
先看看兩個數據結構
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.
}
Pool是提供給外部使用的對象。其中的local成員的真實類型是一個poolLocal數組,localSize是數組長度。poolLocal是真正保存數據的地方。priveate保存了一個臨時對象,shared是保存臨時對象的數組。
為什么Pool中需要這么多poolLocal對象呢?實際上,Pool是給每個線程分配了一個poolLocal對象。也就是說local數組的長度,就是工作線程的數量(size := runtime.GOMAXPROCS(0))。當多線程在並發讀寫的時候,通常情況下都是在自己線程的poolLocal中存取數據。當自己線程的poolLocal中沒有數據時,才會嘗試加鎖去其他線程的poolLocal中“偷”數據。
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()
}
Pool.Get的時候,首先會在local數組中獲取當前線程對應的poolLocal對象。如果private中有數據,則取出來直接返回。如果沒有則先鎖住shared,有數據則直接返回。
為什么這里要鎖住。答案在getSlow中。因為當shared中沒有數據的時候,會嘗試去其他的poolLocal的shared中偷數據。
Go語言的goroutine雖然可以創建很多,但是真正能物理上並發運行的goroutine數量是有限的,是由runtime.GOMAXPROCS(0)設置的。所以這個Pool高效的設計的地方就在於將數據分散在了各個真正並發的線程中,每個線程優先從自己的poolLocal中獲取數據,很大程度上降低了鎖競爭。
