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