深入理解Go語言對象池
對象池是一種在編程中用于優(yōu)化資源管理的技術(shù)。它的基本思想是在應(yīng)用程序啟動時預(yù)先創(chuàng)建一組對象,并在需要時重復(fù)使用這些對象,而不是頻繁地創(chuàng)建和銷毀。這種重用的機(jī)制有助于減少資源分配和回收的開銷,提高程序性能,特別在涉及大量短壽命對象的場景下效果顯著。
在Go語言中,對象池通常通過sync.Pool包或自定義數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)。該機(jī)制利用Go的垃圾回收策略,通過避免不必要的對象分配來減輕垃圾回收的負(fù)擔(dān)。對象的創(chuàng)建、重用和釋放是對象池的核心流程,其中創(chuàng)建發(fā)生在對象池為空且需要新對象時,重用則是從對象池中獲取現(xiàn)有對象,而釋放則是將不再需要的對象放回對象池供其他地方使用。
對象池在高并發(fā)和高性能的Go應(yīng)用中具有廣泛應(yīng)用。例如,在網(wǎng)絡(luò)編程中,可以使用對象池來維護(hù)連接池,避免頻繁地創(chuàng)建和關(guān)閉連接;在數(shù)據(jù)庫訪問中,對象池可以用于管理數(shù)據(jù)庫連接,減少連接的創(chuàng)建和銷毀開銷。這些實(shí)際應(yīng)用場景充分展示了對象池在提升性能和資源利用率方面的價值。
之前在Java性能測試當(dāng)中也分享了通用池化框架 Apache common-pool2 以及對應(yīng)的實(shí)踐案例,今天分享一下Go語言在對象池實(shí)現(xiàn)上的應(yīng)用。
對象池的優(yōu)勢
這里不得不簡單分享一下Go語言的垃圾回收。垃圾回收(Garbage Collection,GC)是一種自動管理內(nèi)存的機(jī)制,用于檢測和釋放不再使用的內(nèi)存對象,以防止內(nèi)存泄漏。Go的垃圾回收機(jī)制采用了基于并發(fā)的標(biāo)記-清理算法,以及部分停頓的方式來進(jìn)行垃圾回收。
Go語言中,頻繁創(chuàng)建對象和回收對象會帶來兩個性能問題。
- 頻繁分配和銷毀對象會造成更多的內(nèi)存碎片,處理這些碎片會增加額外資源開銷。
- 頻繁分配和銷毀對象會導(dǎo)致更頻繁的停頓時間。
- 頻繁分配和銷毀對象會帶來更多系統(tǒng)資源開銷。
為了解決這個問題,處理在優(yōu)化編碼質(zhì)量和調(diào)整GC參數(shù)之外,對象池技術(shù)是最重要的解決方案。以上三個問題均轉(zhuǎn)化為對象池技術(shù)的優(yōu)點(diǎn),
在高性能編程實(shí)踐中,對象池技術(shù)是一項不可或缺的戰(zhàn)略,它不僅能顯著提升系統(tǒng)性能,降低資源開銷,還有助于優(yōu)化內(nèi)存利用率。通過巧妙地重用已經(jīng)存在的對象,對象池有效地規(guī)避了頻繁的對象創(chuàng)建和銷毀過程,減輕了系統(tǒng)負(fù)擔(dān)。這對于面臨資源稀缺、要求高度響應(yīng)性的應(yīng)用環(huán)境尤為重要。
在高并發(fā)場景下,對象池更是發(fā)揮了巨大的作用。并發(fā)環(huán)境中,多個線程或協(xié)程可以從對象池中獲取對象,實(shí)現(xiàn)了資源的共享與協(xié)同,有效提高了程序的并發(fā)性能。同時,對象池還有助于避免由于頻繁的資源分配導(dǎo)致的內(nèi)存碎片問題,優(yōu)化了內(nèi)存空間的使用,使系統(tǒng)更為穩(wěn)定。
在一個長時間運(yùn)行的高性能應(yīng)用中,對象池的靈活性也是其優(yōu)勢之一。通過動態(tài)調(diào)整對象池的大小,可以根據(jù)實(shí)際需求進(jìn)行優(yōu)化,確保在不同負(fù)載下仍然能夠保持高效的性能表現(xiàn)。綜合而言,對象池技術(shù)的采用在高性能編程中不僅是一項優(yōu)秀的實(shí)踐,更是為了應(yīng)對復(fù)雜、高并發(fā)應(yīng)用場景的必備利器。
sync.Pool實(shí)現(xiàn)對象池
首先,Go語言自帶了 sync.Pool 實(shí)現(xiàn)。sync.Pool 是 Go 語言標(biāo)準(zhǔn)庫中的一個對象池實(shí)現(xiàn),用于提高對象的重用性,減少對象的創(chuàng)建和垃圾回收的開銷。sync.Pool 在并發(fā)環(huán)境中特別有用,它能夠顯著提升程序性能。
以下是 sync.Pool 的主要特點(diǎn)和使用方式:
- 對象池的創(chuàng)建: 通過 sync.Pool,你可以創(chuàng)建一個對象池,用于存儲和管理特定類型的對象。對象池中的對象在被取出后可以被重用,而不是每次都重新創(chuàng)建。
- Get 和 Put 操作: 使用 sync.Pool 的 Get 方法可以從對象池中獲取一個對象,而 Put 方法則用于將對象放回對象池。這兩個操作是并發(fā)安全的,可以被多個 goroutine 同時使用。
- 對象的生命周期: sync.Pool 并不保證對象會一直存在,對象可能會在任意時刻被垃圾回收。因此,不能假設(shè)對象在調(diào)用 Get 后一直有效,需要重新初始化。
- 適用于短生命周期對象: sync.Pool 特別適用于管理短生命周期的對象,例如臨時對象、緩存對象等。對于長時間生存的對象,sync.Pool 的優(yōu)勢可能會減弱。
下面是我用 sync.Pool 創(chuàng)建對象池的演示Demo:
package pool
import (
"funtester/ftool"
"log" "sync" "testing")
// PooledObject
// @Description: 對象池對象
type PooledObject struct {
Name string
Age int
Address string
}
// NewObject
//
// @Description: 創(chuàng)建對象
// @return *PooledObject
func NewObject() *PooledObject {
log.Println("創(chuàng)建對象")
return &PooledObject{
Name: "",
Age: 0,
Address: "",
}
}
// Reset
//
// @Description: 重置對象
// @receiver m 對象
func (m *PooledObject) Reset() {
m.Name = ""
m.Age = 0
m.Address = ""
log.Println("重置對象")
}
type ObjectPool struct {
ObjPool sync.Pool
Name string
}
// NewPool
//
// @Description: 創(chuàng)建對象池
// @param size 對象池大小
// @return *ObjectPool 對象類型
func NewPool(size int) *ObjectPool {
return &ObjectPool{
Name: "FunTester測試",
ObjPool: sync.Pool{New: func() interface{} { return NewObject() }},
}
}
// Get
//
// @Description: 獲取對象
// @receiver p 對象池
// @return *PooledObject 對象
func (p *ObjectPool) Get() *PooledObject {
return p.ObjPool.Get().(*PooledObject)
}
// Back
//
// @Description: 回收對象
// @receiver p 對象池
// @param obj 回收的對象
func (p *ObjectPool) Back(obj *PooledObject) {
obj.Reset()
p.ObjPool.Put(obj)
}
func TestPool1(t *testing.T) {
pool := NewPool(1)
get := pool.Get()
get.Name = "FunTester"
get.Age = 18
get.Address = "地球"
log.Printf("%T %s", get, ftool.ToString(get))
pool.Back(get)
get2 := pool.Get()
log.Printf("%T %s", get, ftool.ToString(get2))
}
控制臺打印:
=== RUN TestPool1
2024/01/19 23:05:17 創(chuàng)建對象
2024/01/19 23:05:17 *pool.PooledObject &{FunTester 18 地球}
2024/01/19 23:05:17 重置對象
2024/01/19 23:05:17 *pool.PooledObject &{ 0 }
--- PASS: TestPool1 (0.00s)
PASS
PS:這里不建議使用并發(fā)安全類來控制對象池數(shù)量,因為在使用過程中,對象池中的對象可能會被垃圾回收機(jī)制銷毀,會導(dǎo)致額外的未知問題。但是可以使用并發(fā)安全類進(jìn)行借出和歸還的計數(shù),從而實(shí)現(xiàn)對最大可借數(shù)量的限制,不過略微復(fù)雜,并不適用于性能測試中的場景。
chan實(shí)現(xiàn)對象池
我們還可以借助 chan 來實(shí)現(xiàn)對象池??梢园?chan 用來存儲對象,借和還都只是從 chan 中取出和放入對象。這樣做的好處如下幾點(diǎn):
- 并發(fā)安全。由于 chan 操作是原子性的,所以整個的借還過程都是并發(fā)安全的。
- 數(shù)量可控??梢酝ㄟ^設(shè)置 chan 的容量控制對象總量。
- 阻塞處理。當(dāng)無足夠?qū)ο蠡蛘哌^多對象時,可以阻塞以便進(jìn)行邏輯處理。
- 可讀性好。使用 chan 實(shí)現(xiàn)對象池,代碼清晰易讀,便于維護(hù)。
下面是我的實(shí)現(xiàn)Demo:
package pool
import (
"log"
"reflect" "testing")
type ObjectPool2 struct {
objects chan *PooledObject
Name string
}
// NewPool
//
// @Description: 創(chuàng)建對象池
// @param size 對象池大小
// @return *ObjectPool 對象類型
func NewPool2(size int) *ObjectPool2 {
return &ObjectPool2{
objects: make(chan *PooledObject, size),
Name: "FunTester測試",
}
}
// Get
//
// @Description: 獲取對象
// @receiver p 對象池
// @return *PooledObject 對象
func (p *ObjectPool2) Get2() *PooledObject {
select {
case obj := <-p.objects:
return obj
default:
log.Println("額外創(chuàng)建對象")
return NewObject()
}
}
// Back
//
// @Description: 回收對象
// @receiver p 對象池
// @param obj 回收的對象
func (p *ObjectPool2) Back(obj *PooledObject) {
obj.Reset()
select {
case p.objects <- obj:
default:
obj = nil
log.Println("丟棄對象")
}
}
func TestPool2(t *testing.T) {
pool := NewPool2(1)
get := pool.Get2()
object := pool.Get2()
log.Printf("%T", get)
log.Println(reflect.TypeOf(get))
pool.Back(get)
pool.Back(object)
}
控制臺輸出:
=== RUN TestPool2
2024/01/19 23:19:42 額外創(chuàng)建對象
2024/01/19 23:19:42 創(chuàng)建對象
2024/01/19 23:19:42 額外創(chuàng)建對象
2024/01/19 23:19:42 創(chuàng)建對象
2024/01/19 23:19:42 *pool.PooledObject
2024/01/19 23:19:42 *pool.PooledObject
2024/01/19 23:19:42 重置對象
2024/01/19 23:19:42 重置對象
2024/01/19 23:19:42 丟棄對象
--- PASS: TestPool2 (0.00s)
PASS
雖然chan實(shí)現(xiàn)對象池在某些場景下具有優(yōu)勢,但在其他情況下可能不是最佳選擇。在一些性能要求較高的場景中,使用更為專業(yè)的對象池庫或者手動管理對象池的方式可能更為靈活和高效。
第三方庫
在Go語言中,有一些第三方庫專門用于實(shí)現(xiàn)對象池,它們提供了更復(fù)雜、靈活、高效的對象池管理機(jī)制。以下是一些常用的第三方庫,用于實(shí)現(xiàn)對象池:
github.com/fatih/pool:
GitHub 地址: fatih/pool
該庫提供了一個通用的對象池實(shí)現(xiàn),支持對任意對象的池化。它允許你自定義對象的創(chuàng)建、銷毀和驗證邏輯,非常靈活。
github.com/panjf2000/ants/v2:
GitHub 地址: panjf2000/ants
該庫是一個高性能的 goroutine 池,適用于需要并發(fā)執(zhí)行任務(wù)的場景。雖然主要關(guān)注 goroutine 池,但也可以用作通用的對象池。
github.com/jolestar/go-commons-pool:
GitHub 地址: jolestar/go-commons-pool
該庫是一個通用的對象池實(shí)現(xiàn),支持池化各種類型的對象。它提供了豐富的配置選項,允許你自定義對象創(chuàng)建、銷毀和驗證的邏輯。
github.com/avast/retry-go:
GitHub 地址: avast/retry-go
該庫提供了一個靈活的對象池實(shí)現(xiàn),支持對獲取和釋放對象的重試策略。適用于需要在獲取對象時進(jìn)行重試的情況。
這些庫提供了比標(biāo)準(zhǔn)庫的 sync.Pool 和 chan實(shí)現(xiàn) 更為復(fù)雜且靈活,可以根據(jù)具體需求進(jìn)行選擇。后面有機(jī)會我會選擇其中一兩種學(xué)習(xí)實(shí)踐,然后分享。
到此這篇關(guān)于深入理解Go語言對象池的文章就介紹到這了,更多相關(guān)Go語言對象池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang中優(yōu)秀的消息隊列NSQ基礎(chǔ)安裝及使用詳解
這篇文章主要介紹了Golang中優(yōu)秀的消息隊列NSQ基礎(chǔ)安裝及使用詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12

