真實世界的 Go 設計模式 - 對象池模式
對象池(object pool pattern)是一種設計模式。一個對象池包含一組已經初始化過且可以使用的對象,而可以在有需求時創建和銷燬對象。池的用戶可以從池子中取得對象,對其進行操作處理,並在不需要時歸還給池子而非直接銷燬它。這是一種特殊的工廠對象。
若初始化、實例化的代價高,且有需求需要經常實例化,但每次實例化的數量較少的情況下,使用對象池可以獲得顯著的效能提升。從池子中取得對象的時間是可預測的,但新建一個實例所需的時間是不確定。
另外,利用對象池,我們可以重用對象,減少對象的分配,對於垃圾回收的編程語言,也是一種提高性能的手段。
sync.Pool 是 Go 標準庫 sync 包中的一個非常有用的結構, 它可以用來管理和重用臨時對象, 以減少對象頻繁分配和回收的開銷。
sync.Pool 的主要特點包括:
-
Pool 維護一個可存儲和共享臨時對象的集合, 這些對象無需頻繁地申請和回收內存。
-
Pool 通過 New 字段指定一個函數, 用來生成新對象放入 pool 中。
-
通過 Get 方法可以從 pool 中獲得一個可重用對象, 使用後調用 Put 方法將其放回 pool。
-
對象在不再需要時不會立即銷燬, 而是被保留在 Pool 中以便後續重用。
-
Pool 會根據需求自動調整對象個數, 會定期 GC 不再使用的對象。
-
每個 P 都有一個本地 pool,goroutine 優先從本地 pool 獲取和放回對象。
通過重用對象, 可以減少內存分配和垃圾回收的開銷, 特別適合用於管理許多臨時對象的場景, 如處理大量併發請求時的緩衝、連接等。
比如 net/rpc 包就使用了鏈表來管理常用的 Response、Request 等對象的重用。
Go 標準庫 database/sql 包實現了一個連接池, 可以重用數據庫連接。使用 DB.Conn() 獲取連接對象, 操作完成後調用 conn.Close() 將其放回池中。該連接池默認最大空閒連接數 2, 可以通過 SetMaxIdleConns 進行調整。
fatih/pool[1] 也是一個常用的網絡連接池。
還有一類是 goroutine pool, 也又叫做 worker pool 的,這類的庫就很多的,而且還有人源源不斷的造輪子。比如常用的比如:
-
Jeffail/tunny[2]
-
panjf2000/ants[3]
-
ivpusic/grpool[4]
-
alitto/pond[5]
因爲使用 channel + goroutine 很容易實現 goroutine pool, 所以也有很多關注度不高的輪子,都是作者針對自己的需求定製和改造的。使用也很方便。
Go 標準庫還有一個實驗性的包arena
, 本來就在 Go 1.20 中就要推出的,但是在實現的過程中發現有問題,就一直沒有暴露出來。proposal: arena: new package providing memory arenas [6] 這個提案詳細介紹了內容。你也可以把它看成是一個內存池,從其中產生的對象不會被垃圾回收掉,能夠提升程序的性能。
參考資料
[1]
fatih/pool: https://github.com/fatih/pool
[2]
Jeffail/tunny: https://github.com/Jeffail/tunny
[3]
panjf2000/ants: https://github.com/panjf2000/ants
[4]
ivpusic/grpool: https://github.com/ivpusic/grpool
[5]
alitto/pond: https://github.com/alitto/pond
[6]
proposal: arena: new package providing memory arenas : https://github.com/golang/go/issues/51317
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/p5lv_yTUbxsNYDmZiIpGTw