使用Go語言實(shí)現(xiàn)一個簡單的無界資源池
寫在文章開頭
我們希望通過go語言實(shí)現(xiàn)一個簡單的資源池,而這個資源池的資源包括但不限于:
- 數(shù)據(jù)庫連接池
- 線程池
- 協(xié)程池
- 網(wǎng)絡(luò)連接池
只要這些資源實(shí)現(xiàn)我們指定的關(guān)閉方法,則都可以通過我們封裝的資源池進(jìn)行統(tǒng)一管理,需要簡單說明一下這個資源池的要求:
- 需要用戶指定資源以及資源的創(chuàng)建方法。
- 當(dāng)協(xié)程通過
Acquire方法獲取資源時,若發(fā)現(xiàn)當(dāng)前池中有資源可以分配則直接返回,若沒有足夠的資源則基于傳入的創(chuàng)建方法創(chuàng)建一個全新的資源分配。 - 支持資源釋放和資源池關(guān)閉。

聽起來很像是Java的無界線程池,接下來我們就基于這個需求實(shí)現(xiàn)一個版本。

需求落地
給出資源池結(jié)構(gòu)
我們首先需要給出資源池的結(jié)構(gòu),很明顯作為一個資源池它需要有一個管理資源池的channel,為了保證多協(xié)程競爭資源的協(xié)程安全,我們還需要通過一把Mutex完成操作互斥,同時給出創(chuàng)建資源的工廠方法要求這個工廠方法創(chuàng)建的資源具備資源關(guān)閉能力:
// Pool 定義一個結(jié)構(gòu)體 包含重量級鎖 有緩沖區(qū)Chanel 工廠方法 連接池關(guān)閉狀態(tài)
type Pool struct {
m sync.Mutex
resource chan io.Closer
factory func() (io.Closer, error)
closed bool
}
創(chuàng)建資源池
有個上述的定義之后,我們的創(chuàng)建方法就很容易實(shí)現(xiàn)了,只需基于外部的size和工廠方法完成Pool成員變量初始化即可:
var ErrPoolClosed = errors.New("連接池已關(guān)閉")
func New(fn func() (io.Closer, error), size uint) (*Pool, error) {
//判斷size大小是否合法
if size <= 0 {
return nil, errors.New("size不合法")
}
//基于工廠方法和size創(chuàng)建資源池
return &Pool{
resource: make(chan io.Closer, size),
factory: fn,
}, nil
}
獲取資源
當(dāng)協(xié)程需要獲取資源時,會查看當(dāng)前緩沖通道是否有足夠的資源,如果有則在正確運(yùn)行的情況下返回出去,反之基于我們上文傳入的工廠方法完成資源創(chuàng)建并返回:
func (p *Pool) Acquire() (io.Closer, error) {
select {
//如果channel有足夠的資源分配則直接返回
case r, ok := <-p.resource:
if !ok {
log.Println("連接池已關(guān)閉")
return nil, ErrPoolClosed
}
log.Println("拿到連接池共享資源")
return r, nil
//基于工廠方法創(chuàng)建全新的資源返回出去
default:
log.Println("資源不足,創(chuàng)建新的連接資源")
return p.factory()
}
}
釋放與關(guān)閉
這里我們將資源的釋放和關(guān)閉放在一起說明,在進(jìn)行資源釋放和關(guān)閉時我們需要考慮3個問題即:
- 已關(guān)閉的資源池?zé)o需歸還資源。
- 正在關(guān)閉資源池時不可歸還資源。
- 正在歸還資源時不可關(guān)閉資源池。
所以進(jìn)行這兩個操作時,我們需要通過互斥鎖確保兩個操作互斥:
// Release 上鎖 設(shè)置方法退出后解鎖 查看當(dāng)前連接池是否已關(guān)閉,若關(guān)閉則直接將資源關(guān)閉 ,反之select查看能否將其存入緩沖區(qū),若可以輸出入隊成功,反之輸出隊列已滿
func (p *Pool) Release(r io.Closer) {
//上鎖確保關(guān)閉和歸還資源操作互斥
p.m.Lock()
//函數(shù)退出時解鎖
defer p.m.Unlock()
//如果資源池關(guān)閉則直接將當(dāng)前資源關(guān)閉銷毀
if p.closed {
log.Println("連接池已關(guān)閉,直接銷毀當(dāng)前資源")
r.Close()
}
//將連接歸還,如果滿了則直接關(guān)閉銷毀
select {
case p.resource <- r:
log.Println("連接歸還成功")
default:
log.Println("連接池已滿,資源直接銷毀")
r.Close()
}
}
// Close 方法 上鎖 設(shè)置方法退出后解鎖 遍歷所有資源將其關(guān)閉 然后再關(guān)閉連接池
func (p *Pool) Close() {
p.m.Lock()
defer p.m.Unlock()
if p.closed {
log.Println("連接池已關(guān)閉,直接銷毀當(dāng)前資源")
return
}
//設(shè)置為關(guān)閉
p.closed = true
//關(guān)閉資源
close(p.resource)
//遍歷資源池資源
for r := range p.resource {
r.Close()
}
}
測試代碼與輸出
最后我們給出測試代碼,可以看到我們基于資源池工具類模擬數(shù)據(jù)庫連接池的管理:
//設(shè)置最大協(xié)程數(shù)與資源池數(shù)為24
const maxGoroutines = 24
const poolResources = 24
//創(chuàng)建可關(guān)閉的數(shù)據(jù)庫連接
type dbConnection struct {
ID int32
}
//對應(yīng)的關(guān)閉方法
func (d *dbConnection) Close() error {
log.Println("當(dāng)前數(shù)據(jù)庫連接", d.ID, "已關(guān)閉")
return nil
}
var idCounter int32
func createConnection() (io.Closer, error) {
id := atomic.AddInt32(&idCounter, 1)
return &dbConnection{ID: id}, nil
}
func main() {
//創(chuàng)建maxGoroutines個WaitGroup
var wg sync.WaitGroup
wg.Add(maxGoroutines)
//傳入createConnection方法和連接池大小poolResources創(chuàng)建數(shù)據(jù)庫連接池
p, err := pool.New(createConnection, poolResources)
if err != nil {
log.Println(err)
}
//創(chuàng)建24個協(xié)程獲取資源
for i := 0; i < maxGoroutines; i++ {
go func(queryParam int) {
queryData(queryParam, p)
defer wg.Done()
}(i)
}
//等待操作完成關(guān)閉連接池
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)
}
同時我們給出輸出結(jié)果:
2024/05/05 23:36:10 資源不足,創(chuàng)建新的連接資源
2024/05/05 23:36:10 資源不足,創(chuàng)建新的連接資源
2024/05/05 23:36:10 資源不足,創(chuàng)建新的連接資源
2024/05/05 23:36:10 資源不足,創(chuàng)建新的連接資源
2024/05/05 23:36:10 資源不足,創(chuàng)建新的連接資源
2024/05/05 23:36:10 資源不足,創(chuàng)建新的連接資源
2024/05/05 23:36:10 資源不足,創(chuàng)建新的連接資源
2024/05/05 23:36:10 資源不足,創(chuàng)建新的連接資源
2024/05/05 23:36:10 資源不足,創(chuàng)建新的連接資源
2024/05/05 23:36:10 資源不足,創(chuàng)建新的連接資源
2024/05/05 23:36:10 資源不足,創(chuàng)建新的連接資源
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 連接歸還成功
.......
小結(jié)
到此這篇關(guān)于使用Go實(shí)現(xiàn)一個簡單的無界資源池的文章就介紹到這了,更多相關(guān)Go無界資源池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang執(zhí)行命令獲取執(zhí)行結(jié)果狀態(tài)(推薦)
這篇文章主要介紹了golang執(zhí)行命令獲取執(zhí)行結(jié)果狀態(tài)的相關(guān)知識,非常不錯,具有一定的參考借鑒價值,需要的朋友參考下吧2019-11-11
Golang標(biāo)準(zhǔn)庫os/exec執(zhí)行外部命令并獲取其輸出包代碼示例
這篇文章主要為大家介紹了Golang標(biāo)準(zhǔn)庫os/exec執(zhí)行外部命令并獲取其輸出包代碼示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
一文弄懂用Go實(shí)現(xiàn)MCP服務(wù)的示例代碼
本文主要介紹了一文弄懂用Go實(shí)現(xiàn)MCP服務(wù)的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-04-04
Golang實(shí)現(xiàn)斷點(diǎn)續(xù)傳功能
這篇文章主要為大家詳細(xì)介紹了Golang實(shí)現(xiàn)斷點(diǎn)續(xù)傳、復(fù)制文件功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-07-07
Golang中如何使用lua進(jìn)行擴(kuò)展詳解
這篇文章主要給大家介紹了關(guān)于Golang中如何使用lua進(jìn)行擴(kuò)展的相關(guān)資料,這是最近在工作中遇到的一個問題,覺著有必要分享出來給大家學(xué)習(xí),文中給出了詳細(xì)的示例,需要的朋友可以參考借鑒,下面來一起看看吧。2017-10-10

