用 Go 實現一個無界資源池
寫在文章開頭
我們希望通過 go 語言實現一個簡單的資源池,而這個資源池的資源包括但不限於:
-
數據庫連接池
-
線程池
-
協程池
-
網絡連接池
只要這些資源實現我們指定的關閉方法,則都可以通過我們封裝的資源池進行統一管理,需要簡單說明一下這個資源池的要求:
-
需要用戶指定資源以及資源的創建方法。
-
當協程通過
Acquire
方法獲取資源時,若發現當前池中有資源可以分配則直接返回,若沒有足夠的資源則基於傳入的創建方法創建一個全新的資源分配。 -
支持資源釋放和資源池關閉。
聽起來很像是 Java 的無界線程池,接下來我們就基於這個需求實現一個版本。
Hi,我是 sharkChili ,是個不斷在硬核技術上作死的 java coder ,是 CSDN 的博客專家 ,也是開源項目 Java Guide 的維護者之一,熟悉 Java 也會一點 Go ,偶爾也會在 C 源碼 邊緣徘徊。寫過很多有意思的技術博客,也還在研究並輸出技術的路上,希望我的文章對你有幫助,非常歡迎你關注我的公衆號: 寫代碼的 SharkChili 。
需求落地
給出資源池結構
我們首先需要給出資源池的結構,很明顯作爲一個資源池它需要有一個管理資源池的 channel,爲了保證多協程競爭資源的協程安全,我們還需要通過一把 Mutex 完成操作互斥,同時給出創建資源的工廠方法要求這個工廠方法創建的資源具備資源關閉能力:
// Pool 定義一個結構體 包含重量級鎖 有緩衝區Chanel 工廠方法 連接池關閉狀態
type Pool struct {
m sync.Mutex
resource chan io.Closer
factory func() (io.Closer, error)
closed bool
}
創建資源池
有個上述的定義之後,我們的創建方法就很容易實現了,只需基於外部的 size 和工廠方法完成 Pool 成員變量初始化即可:
var ErrPoolClosed = errors.New("連接池已關閉")
func New(fn func() (io.Closer, error), size uint) (*Pool, error) {
//判斷size大小是否合法
if size <= 0 {
return nil, errors.New("size不合法")
}
//基於工廠方法和size創建資源池
return &Pool{
resource: make(chan io.Closer, size),
factory: fn,
}, nil
}
獲取資源
當協程需要獲取資源時,會查看當前緩衝通道是否有足夠的資源,如果有則在正確運行的情況下返回出去,反之基於我們上文傳入的工廠方法完成資源創建並返回:
func (p *Pool) Acquire() (io.Closer, error) {
select {
//如果channel有足夠的資源分配則直接返回
case r, ok := <-p.resource:
if !ok {
log.Println("連接池已關閉")
return nil, ErrPoolClosed
}
log.Println("拿到連接池共享資源")
return r, nil
//基於工廠方法創建全新的資源返回出去
default:
log.Println("資源不足,創建新的連接資源")
return p.factory()
}
}
釋放與關閉
這裏我們將資源的釋放和關閉放在一起說明,在進行資源釋放和關閉時我們需要考慮 3 個問題即:
-
已關閉的資源池無需歸還資源。
-
正在關閉資源池時不可歸還資源。
-
正在歸還資源時不可關閉資源池。
所以進行這兩個操作時,我們需要通過互斥鎖確保兩個操作互斥:
// Release 上鎖 設置方法退出後解鎖 查看當前連接池是否已關閉,若關閉則直接將資源關閉 ,反之select查看能否將其存入緩衝區,若可以輸出入隊成功,反之輸出隊列已滿
func (p *Pool) Release(r io.Closer) {
//上鎖確保關閉和歸還資源操作互斥
p.m.Lock()
//函數退出時解鎖
defer p.m.Unlock()
//如果資源池關閉則直接將當前資源關閉銷燬
if p.closed {
log.Println("連接池已關閉,直接銷燬當前資源")
r.Close()
}
//將連接歸還,如果滿了則直接關閉銷燬
select {
case p.resource <- r:
log.Println("連接歸還成功")
default:
log.Println("連接池已滿,資源直接銷燬")
r.Close()
}
}
// Close 方法 上鎖 設置方法退出後解鎖 遍歷所有資源將其關閉 然後再關閉連接池
func (p *Pool) Close() {
p.m.Lock()
defer p.m.Unlock()
if p.closed {
log.Println("連接池已關閉,直接銷燬當前資源")
return
}
//設置爲關閉
p.closed = true
//關閉資源
close(p.resource)
//遍歷資源池資源
for r := range p.resource {
r.Close()
}
}
測試代碼與輸出
最後我們給出測試代碼,可以看到我們基於資源池工具類模擬數據庫連接池的管理:
//設置最大協程數與資源池數爲24
const maxGoroutines = 24
const poolResources = 24
//創建可關閉的數據庫連接
type dbConnection struct {
ID int32
}
//對應的關閉方法
func (d *dbConnection) Close() error {
log.Println("當前數據庫連接", d.ID, "已關閉")
return nil
}
var idCounter int32
func createConnection() (io.Closer, error) {
id := atomic.AddInt32(&idCounter, 1)
return &dbConnection{ID: id}, nil
}
func main() {
//創建maxGoroutines個WaitGroup
var wg sync.WaitGroup
wg.Add(maxGoroutines)
//傳入createConnection方法和連接池大小poolResources創建數據庫連接池
p, err := pool.New(createConnection, poolResources)
if err != nil {
log.Println(err)
}
//創建24個協程獲取資源
for i := 0; i < maxGoroutines; i++ {
go func(queryParam int) {
queryData(queryParam, p)
defer wg.Done()
}(i)
}
//等待操作完成關閉連接池
wg.Wait()
log.Println("查詢完成")
p.Close()
}
//queryData 基於連接池Acquire獲取資源,完成後通過Release歸還資源
func queryData(queryParam int, p *pool.Pool) {
r, e := p.Acquire()
if e != nil {
log.Println(e)
return
}
defer p.Release(r)
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
log.Println("查詢", queryParam, "使用連接", r.(*dbConnection).ID)
}
同時我們給出輸出結果:
2024/05/05 23:36:10 資源不足,創建新的連接資源
2024/05/05 23:36:10 資源不足,創建新的連接資源
2024/05/05 23:36:10 資源不足,創建新的連接資源
2024/05/05 23:36:10 資源不足,創建新的連接資源
2024/05/05 23:36:10 資源不足,創建新的連接資源
2024/05/05 23:36:10 資源不足,創建新的連接資源
2024/05/05 23:36:10 資源不足,創建新的連接資源
2024/05/05 23:36:10 資源不足,創建新的連接資源
2024/05/05 23:36:10 資源不足,創建新的連接資源
2024/05/05 23:36:10 資源不足,創建新的連接資源
2024/05/05 23:36:10 資源不足,創建新的連接資源
2024/05/05 23:36:10 查詢 17 使用連接 14
2024/05/05 23:36:10 連接歸還成功
2024/05/05 23:36:10 查詢 5 使用連接 5
2024/05/05 23:36:10 連接歸還成功
2024/05/05 23:36:10 查詢 3 使用連接 2
2024/05/05 23:36:10 連接歸還成功
2024/05/05 23:36:10 查詢 19 使用連接 19
2024/05/05 23:36:10 連接歸還成功
.......
小結
以上便是筆者對於無界資源池的實現思路,希望對你有幫助。
我是 sharkchili ,CSDN Java 領域博客專家,開源項目—JavaGuide contributor,我想寫一些有意思的東西,希望對你有幫助,如果你想實時收到我寫的硬核的文章也歡迎你關注我的公衆號: 寫代碼的 SharkChili 。 因爲近期收到很多讀者的私信,所以也專門創建了一個交流羣,感興趣的讀者可以通過上方的公衆號獲取筆者的聯繫方式完成好友添加,點擊備註 “加羣” 即可和筆者和筆者的朋友們進行深入交流。
參考
《go in action》
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/EaYwgWF81fkHEy3TpXIdwQ