前言
在 golang 中有一個(gè)池,它特別神奇,你只要和它有個(gè)約定,你要什么它就給什么,你用完了還可以還回去,但是下次拿的時(shí)候呢,確不一定是你上次存的那個(gè),這個(gè)池就是 sync.Pool
說實(shí)話第一次看到這個(gè)東西的時(shí)候,真的想不到這個(gè)東西有啥用啊,為什么要有這個(gè)東西呢?等我看完之后,嗯,還有有點(diǎn)用的;等到有一次優(yōu)化經(jīng)歷的時(shí)候,嗯,這個(gè)有點(diǎn)意思了。今天我們就來看看這個(gè)神奇的 sync.Pool
簡單案例
首先我們來看看這個(gè) sync.Pool 是如何使用的,其實(shí)非常的簡單。
它一共只有三個(gè)方法我們需要知道的:New、Put、Get
package main
import (
"fmt"
"sync"
)
var strPool = sync.Pool{
New: func() interface{} {
return "test str"
},
}
func main() {
str := strPool.Get()
fmt.Println(str)
strPool.Put(str)
}
- 通過
New去定義你這個(gè)池子里面放的究竟是什么東西,在這個(gè)池子里面你只能放一種類型的東西。比如在上面的例子中我就在池子里面放了字符串。 - 我們隨時(shí)可以通過
Get方法從池子里面獲取我們之前在New里面定義類型的數(shù)據(jù)。 - 當(dāng)我們用完了之后可以通過
Put方法放回去,或者放別的同類型的數(shù)據(jù)進(jìn)去。
目的
那么這個(gè)池子的目的是什么呢?其實(shí)一句話就可以說明白,就是為了復(fù)用已經(jīng)使用過的對象,來達(dá)到優(yōu)化內(nèi)存使用和回收的目的。說白了,一開始這個(gè)池子會(huì)初始化一些對象供你使用,如果不夠了呢,自己會(huì)通過new產(chǎn)生一些,當(dāng)你放回去了之后這些對象會(huì)被別人進(jìn)行復(fù)用,當(dāng)對象特別大并且使用非常頻繁的時(shí)候可以大大的減少對象的創(chuàng)建和回收的時(shí)間。
來看看doc
其實(shí)官方文檔里面給出了一些小細(xì)節(jié)讓我們一起來看看
A Pool is a set of temporary objects that may be individually saved and retrieved.
Any item stored in the Pool may be removed automatically at any time without notification. If the Pool holds the only reference when this happens, the item might be deallocated.
A Pool is safe for use by multiple goroutines simultaneously.
Pool's purpose is to cache allocated but unused items for later reuse, relieving pressure on the garbage collector. That is, it makes it easy to build efficient, thread-safe free lists. However, it is not suitable for all free lists.
An appropriate use of a Pool is to manage a group of temporary items silently shared among and potentially reused by concurrent independent clients of a package. Pool provides a way to amortize allocation overhead across many clients.
An example of good use of a Pool is in the fmt package, which maintains a dynamically-sized store of temporary output buffers. The store scales under load (when many goroutines are actively printing) and shrinks when quiescent.
On the other hand, a free list maintained as part of a short-lived object is not a suitable use for a Pool, since the overhead does not amortize well in that scenario. It is more efficient to have such objects implement their own free list.
A Pool must not be copied after first use.
注意其中加粗的部分,我列一下其中的點(diǎn),建議還是嘗試去閱讀doc里面的說明。
- 臨時(shí)對象
- 自動(dòng)移除
- 當(dāng)這個(gè)對象的引用只有sync.Pool持有時(shí),這個(gè)對象內(nèi)存會(huì)被釋放
- 多線程安全
- 目的就是緩存并重用對象,減少GC的壓力
- 自動(dòng)擴(kuò)容、縮容
- 不要去拷貝pool,也就是說最好單例
源碼分析
下面我們從源碼層面來看看這個(gè) sync.Pool;可能需要你有GPM模型和GC的相關(guān)知識(shí)。
使用golang版本: go version go1.13
結(jié)構(gòu)
type Pool struct {
noCopy noCopy
local unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
localSize uintptr // size of the local array
victim unsafe.Pointer // local from previous cycle
victimSize uintptr // size of victims 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 poolLocalInternal struct {
private interface{} // Can be used only by the respective P.
shared poolChain // Local P can pushHead/popHead; any P can popTail.
}
type poolLocal struct {
poolLocalInternal
// Prevents false sharing on widespread platforms with
// 128 mod (cache line size) = 0 .
pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}
我們可以看到其實(shí)結(jié)構(gòu)并不復(fù)雜,但是如果自己看的話有點(diǎn)懵。注意幾個(gè)細(xì)節(jié)就ok。
- local這里面真正的是[P]poolLocal其中P就是GPM模型中的P,有多少個(gè)P數(shù)組就有多大,也就是每個(gè)P維護(hù)了一個(gè)本地的poolLocal。
- poolLocal里面維護(hù)了一個(gè)private一個(gè)shared,看名字其實(shí)就很明顯了,private是給自己用的,而shared的是一個(gè)隊(duì)列,可以給別人用的。注釋寫的也很清楚,自己可以從隊(duì)列的頭部存然后從頭部取,而別的P可以從尾部取。
- victim這個(gè)從字面上面也可以知道,幸存者嘛,當(dāng)進(jìn)行g(shù)c的stw時(shí)候,會(huì)將local中的對象移到victim中去,也就是說幸存了一次gc,
Get
func (p *Pool) Get() interface{} {
......
l, pid := p.pin()
x := l.private
l.private = nil
if x == nil {
// Try to pop the head of the local shard. We prefer
// the head over the tail for temporal locality of
// reuse.
x, _ = l.shared.popHead()
if x == nil {
x = p.getSlow(pid)
}
}
runtime_procUnpin()
......
if x == nil && p.New != nil {
x = p.New()
}
return x
}
func (p *Pool) getSlow(pid int) interface{} {
// See the comment in pin regarding ordering of the loads.
size := atomic.LoadUintptr(&p.localSize) // load-acquire
locals := p.local // load-consume
// Try to steal one element from other procs.
for i := 0; i < int(size); i++ {
l := indexLocal(locals, (pid+i+1)%int(size))
if x, _ := l.shared.popTail(); x != nil {
return x
}
}
// Try the victim cache. We do this after attempting to steal
// from all primary caches because we want objects in the
// victim cache to age out if at all possible.
size = atomic.LoadUintptr(&p.victimSize)
if uintptr(pid) >= size {
return nil
}
locals = p.victim
l := indexLocal(locals, pid)
if x := l.private; x != nil {
l.private = nil
return x
}
for i := 0; i < int(size); i++ {
l := indexLocal(locals, (pid+i)%int(size))
if x, _ := l.shared.popTail(); x != nil {
return x
}
}
// Mark the victim cache as empty for future gets don't bother
// with it.
atomic.StoreUintptr(&p.victimSize, 0)
return nil
}
我去掉了其中一些競態(tài)分析的代碼,Get的邏輯其實(shí)非常清晰。
- 如果 private 不是空的,那就直接拿來用
- 如果 private 是空的,那就先去本地的shared隊(duì)列里面從頭 pop 一個(gè)
- 如果本地的 shared 也沒有了,那 getSlow 去拿,其實(shí)就是去別的P的 shared 里面偷,偷不到回去 victim 幸存者里面找
- 如果最后都沒有,那就只能調(diào)用 New 方法創(chuàng)建一個(gè)了
我隨手畫了一下,可能不是特別準(zhǔn)確,意思到位了

Put
// Put adds x to the pool.
func (p *Pool) Put(x interface{}) {
if x == nil {
return
}
......
l, _ := p.pin()
if l.private == nil {
l.private = x
x = nil
}
if x != nil {
l.shared.pushHead(x)
}
runtime_procUnpin()
......
}
看完Get其實(shí)Put就很簡單了
- 如果 private 沒有,就放在 private
- 如果 private 有了,那么就放到 shared 隊(duì)列的頭部
實(shí)際測試
讓我們實(shí)際寫個(gè)測試的案例來測測具體使用時(shí)會(huì)有什么樣的變化
Put之后馬上Get
var pool = sync.Pool{
New: func() interface{} {
return "123"
},
}
func main() {
t := pool.Get().(string)
fmt.Println(t)
pool.Put("321")
t2 := pool.Get().(string)
fmt.Println(t2)
}
輸出:
123
321
Put之后GC后Get
var pool = sync.Pool{
New: func() interface{} {
return "123"
},
}
func main() {
t := pool.Get().(string)
fmt.Println(t)
pool.Put("321")
pool.Put("321")
pool.Put("321")
pool.Put("321")
runtime.GC()
time.Sleep(1 * time.Second)
t2 := pool.Get().(string)
fmt.Println(t2)
runtime.GC()
time.Sleep(1 * time.Second)
t2 = pool.Get().(string)
fmt.Println(t2)
}
輸出:
123
321
123
你知道為什么嗎?
總結(jié)
這次總結(jié)來點(diǎn)不一樣的,提幾個(gè)問題吧。
- 什么情況下適合使用sync.Pool呢?
- sync.Pool的對象什么時(shí)候會(huì)被回收呢?
- sync.Pool是如何實(shí)現(xiàn)線程安全的?
如果你能回答上面的問題,證明你對它已經(jīng)足夠了解了,那么就可以嘗試在具體的情況下使用它來玩玩了。試試吧~