前言
在 golang 中有一個(gè)池淤井,它特別神奇,你只要和它有個(gè)約定工猜,你要什么它就給什么米诉,你用完了還可以還回去,但是下次拿的時(shí)候呢篷帅,確不一定是你上次存的那個(gè)史侣,這個(gè)池就是 sync.Pool
說(shuō)實(shí)話第一次看到這個(gè)東西的時(shí)候,真的想不到這個(gè)東西有啥用啊魏身,為什么要有這個(gè)東西呢惊橱?等我看完之后,嗯叠骑,還有有點(diǎn)用的李皇;等到有一次優(yōu)化經(jīng)歷的時(shí)候削茁,嗯宙枷,這個(gè)有點(diǎn)意思了掉房。今天我們就來(lái)看看這個(gè)神奇的 sync.Pool
簡(jiǎn)單案例
首先我們來(lái)看看這個(gè) sync.Pool 是如何使用的,其實(shí)非常的簡(jiǎn)單慰丛。
它一共只有三個(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)
}
- 通過(guò)
New
去定義你這個(gè)池子里面放的究竟是什么東西诅病,在這個(gè)池子里面你只能放一種類型的東西哪亿。比如在上面的例子中我就在池子里面放了字符串。 - 我們隨時(shí)可以通過(guò)
Get
方法從池子里面獲取我們之前在New里面定義類型的數(shù)據(jù)贤笆。 - 當(dāng)我們用完了之后可以通過(guò)
Put
方法放回去蝇棉,或者放別的同類型的數(shù)據(jù)進(jìn)去。
目的
那么這個(gè)池子的目的是什么呢芥永?其實(shí)一句話就可以說(shuō)明白篡殷,就是為了復(fù)用已經(jīng)使用過(guò)的對(duì)象,來(lái)達(dá)到優(yōu)化內(nèi)存使用和回收的目的埋涧。說(shuō)白了板辽,一開(kāi)始這個(gè)池子會(huì)初始化一些對(duì)象供你使用,如果不夠了呢棘催,自己會(huì)通過(guò)new產(chǎn)生一些劲弦,當(dāng)你放回去了之后這些對(duì)象會(huì)被別人進(jìn)行復(fù)用,當(dāng)對(duì)象特別大并且使用非常頻繁的時(shí)候可以大大的減少對(duì)象的創(chuàng)建和回收的時(shí)間醇坝。
來(lái)看看doc
其實(shí)官方文檔里面給出了一些小細(xì)節(jié)讓我們一起來(lái)看看
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里面的說(shuō)明呼猪。
- 臨時(shí)對(duì)象
- 自動(dòng)移除
- 當(dāng)這個(gè)對(duì)象的引用只有sync.Pool持有時(shí)呀袱,這個(gè)對(duì)象內(nèi)存會(huì)被釋放
- 多線程安全
- 目的就是緩存并重用對(duì)象,減少GC的壓力
- 自動(dòng)擴(kuò)容郑叠、縮容
- 不要去拷貝pool夜赵,也就是說(shuō)最好單例
源碼分析
下面我們從源碼層面來(lái)看看這個(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ì)列笑撞,可以給別人用的岛啸。注釋寫(xiě)的也很清楚,自己可以從隊(duì)列的頭部存然后從頭部取茴肥,而別的P可以從尾部取坚踩。
- victim這個(gè)從字面上面也可以知道,幸存者嘛瓤狐,當(dāng)進(jìn)行g(shù)c的stw時(shí)候瞬铸,會(huì)將local中的對(duì)象移到victim中去,也就是說(shuō)幸存了一次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
}
我去掉了其中一些競(jìng)態(tài)分析的代碼嗓节,Get的邏輯其實(shí)非常清晰。
- 如果 private 不是空的皆警,那就直接拿來(lái)用
- 如果 private 是空的赦政,那就先去本地的shared隊(duì)列里面從頭 pop 一個(gè)
- 如果本地的 shared 也沒(méi)有了,那 getSlow 去拿耀怜,其實(shí)就是去別的P的 shared 里面偷恢着,偷不到回去 victim 幸存者里面找
- 如果最后都沒(méi)有,那就只能調(diào)用 New 方法創(chuàng)建一個(gè)了
我隨手畫(huà)了一下财破,可能不是特別準(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就很簡(jiǎn)單了
- 如果 private 沒(méi)有,就放在 private
- 如果 private 有了左痢,那么就放到 shared 隊(duì)列的頭部
實(shí)際測(cè)試
讓我們實(shí)際寫(xiě)個(gè)測(cè)試的案例來(lái)測(cè)測(cè)具體使用時(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é)來(lái)點(diǎn)不一樣的,提幾個(gè)問(wèn)題吧俊性。
- 什么情況下適合使用sync.Pool呢略步?
- sync.Pool的對(duì)象什么時(shí)候會(huì)被回收呢?
- sync.Pool是如何實(shí)現(xiàn)線程安全的定页?
如果你能回答上面的問(wèn)題趟薄,證明你對(duì)它已經(jīng)足夠了解了,那么就可以嘗試在具體的情況下使用它來(lái)玩玩了典徊。試試吧~