目錄
1. 什麼是sync.Pool?
Go 1.3 的sync包中加入一個新特性:Pool,官方文檔。
簡單的說:它就是一個臨時對象池,這個類設計的目的是用來保存和複用臨時對象,以減少內存分配,降低CG壓力。
2. 爲什麼需要sync.Pool?
增加臨時對象的重用率,減少內存分配,減少GC負擔,goroutine對象越多GC越慢,因爲Golang進行三色標記回收的時候,要標記的也越多,自然就慢。
3. sync.Pool使用
思路:搞一個池子,預先放入臨時產生的對象,然後取出使用
官方fmt包就是使用了sync.pool,由於fmt總是需要很多[]byte對象,索性就直接建了一個[]byte對象的池子。
package main
import (
"fmt"
"sync"
)
func main() {
// 初始化一個pool
pool := &sync.Pool{
// 默認的返回值設置,不寫這個參數,默認是nil
New: func() interface{} {
return 0
},
}
// 看一下初始的值,這裏是返回0,如果不設置New函數,默認返回nil
init := pool.Get()
fmt.Println(init)
// 設置一個參數1
pool.Put(1)
// 獲取查看結果
num := pool.Get()
fmt.Println(num)
// 再次獲取,會發現,已經是空的了,只能返回默認的值。
num = pool.Get()
fmt.Println(num)
}
4. sync.Pool源碼分析
1) Pool結構分析
type Pool struct {
// noCopy,防止當前類型被copy,是一個有意思的字段,後文詳說。
noCopy noCopy
// [P]poolLocal 數組指針
local unsafe.Pointer
// 數組大小
localSize uintptr
// 選填的自定義函數,緩衝池無數據的時候會調用,不設置默認返回nil
New func() interface{} //新建對象函數
}
type poolLocalInternal struct {
// 私有緩存區
private interface{}
// 公共緩存區
shared []interface{}
// 鎖
Mutex
}
type poolLocal struct {
// 每個P對應的pool
poolLocalInternal
// 這個字段很有意思,是爲了防止“false sharing/僞共享”,後文詳講。
pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}
2) 基礎函數pin,目的:確定當前P(調度器)綁定的localPool對象
流程:禁止搶佔GC =》尋找偏移量=》檢查越界 =》返回poolLocal=》加鎖重建pool,並添加到allPool
func (p *Pool) pin() *poolLocal {
// 返回當前 P.id && 設置禁止搶佔(避免GC)
pid := runtime_procPin()
// 根據locaSize來獲取當前指針偏移的位置
s := atomic.LoadUintptr(&p.localSize)
l := p.local
// 有可能在運行中動調調整P,所以這裏進行需要判斷是否越界
if uintptr(pid) < s {
// 沒越界,直接返回
return indexLocal(l, pid)
}
// 越界時,會涉及全局加鎖,重新分配poolLocal,添加到全局列表
return p.pinSlow()
}
var (
allPoolsMu Mutex
allPools []*Pool
)
func (p *Pool) pinSlow() *poolLocal {
// 取消P的禁止搶佔(因爲後面要進行metux加鎖)
runtime_procUnpin()
// 加鎖
allPoolsMu.Lock()
defer allPoolsMu.Unlock()
// 返回當前 P.id && 設置禁止搶佔(避免GC)
pid := runtime_procPin()
// 再次檢查是否符合條件,有可能中途已被其他線程調用
s := p.localSize
l := p.local
if uintptr(pid) < s {
return indexLocal(l, pid)
}
// 如果數組爲空,則新建Pool,將其添加到 allPools,GC以此獲取所有 Pool 實例
if p.local == nil {
allPools = append(allPools, p)
}
// 根據 P 數量創建 slice
size := runtime.GOMAXPROCS(0)
local := make([]poolLocal, size)
// 將底層數組起始指針保存到 Pool.local,並設置 P.localSize
// 這裏需要關注的是:如果GOMAXPROCS在GC間發生變化,則會重新分配的時候,直接丟棄老的,等待GC回收。
atomic.StorePointer(&p.local, unsafe.Pointer(&local[0]))
atomic.StoreUintptr(&p.localSize, uintptr(size))
// 返回本次所需的 poolLocal
return &local[pid]
}
// 根據數據結構的大小來計算指針的偏移量
func indexLocal(l unsafe.Pointer, i int) *poolLocal {
lp := unsafe.Pointer(uintptr(l) + uintptr(i)*unsafe.Sizeof(poolLocal{}))
return (*poolLocal)(lp)
}
3) put (優先放入private空間,後面再放入shared空間)
func (p *Pool) Put(x interface{}) {
if x == nil {
return
}
// 這段代碼,不需要關心,降低競爭的
if race.Enabled {
if fastrand()%4 == 0 {
// Randomly drop x on floor.
return
}
race.ReleaseMerge(poolRaceAddr(x))
race.Disable()
}
// 獲取當前的poolLocal
l := p.pin()
// 如果private爲nil,則優先進行設置,並標記x
if l.private == nil {
l.private = x
x = nil
}
runtime_procUnpin()
// 如果標記x不爲nil,則將x設置到shared中
if x != nil {
l.Lock()
l.shared = append(l.shared, x)
l.Unlock()
}
// 設置競爭可用了。
if race.Enabled {
race.Enable()
}
}
4) get
優先從private空間拿,不存在再繼續加鎖從shared空間拿,還沒有再從其他的PoolLocal的shared空間拿,還沒有就直接new一個返回。
func (p *Pool) Get() interface{} {
// 競爭相關的設置
if race.Enabled {
race.Disable()
}
// 獲取當前的poolLocal
l := p.pin()
// 從private中獲取
x := l.private
l.private = nil
runtime_procUnpin()
// 不存在,則繼續從shared空間拿,
if x == nil {
// 加鎖了,防止併發
l.Lock()
last := len(l.shared) - 1
if last >= 0 {
x = l.shared[last]
// 從尾巴開始拿起
l.shared = l.shared[:last]
}
l.Unlock()
if x == nil {
// 從其他的poolLocal中的shared空間看看有沒有可返回的。
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{}) {
// 獲取poolLocal數組的大小
size := atomic.LoadUintptr(&p.localSize) // load-acquire
local := p.local // load-consume
// 嘗試從其他procs獲取一個P對象
pid := runtime_procPin()
runtime_procUnpin()
for i := 0; i < int(size); i++ {
// 獲取一個poolLocal,注意這裏是從當前的local的位置開始獲取的,目的是防止取到自身
l := indexLocal(local, (pid+i+1)%int(size))
// 加鎖從尾部獲取shared的數據
l.Lock()
last := len(l.shared) - 1
// 若長度大於1
if last >= 0 {
x = l.shared[last]
l.shared = l.shared[:last]
l.Unlock()
break
}
l.Unlock()
}
return x
}
5. QA
1) pool的是永久保存的嗎?
會進行清理的,時間就是兩次GC間隔的時間。 sync.Pool不適合放做“數據庫連接池”等帶持久性質的數據,因爲它會定期回收。
2) 爲什麼獲取shared要加鎖,而private不用?
golang是MPG的方式運行的,每個P都分配一個localPool,在同一個P下面只會有一個Gouroutine在跑,所以這裏的private,在同一時間就只可能被一個Gouroutine獲取到。而shared就不一樣了,有可能被其他的P給獲取走,在同一時間就只可能被多個Gouroutine獲取到,爲了保證數據競爭,必須加一個鎖來保證只會被一個G拿走。
3) noCopy的作用?
防止Pool被拷貝,因爲Pool 在Golang是全局唯一。如何實現被防止拷貝:只要包含實現 sync.Locker 這個接口的結構體noCopy,go vet 就可以幫我們進行檢查是否被拷貝。
4) pad的作用?
主要就是用來防止“僞共享”的。
5) 如何保證數據存儲在LocalPool數組對應的單元?
根據數據結構的大小來計算指針的偏移量,進而算出是LocalPool數組的哪個。