Go語言之併發示例-Pool(二)
針對這個資源池管理的一步步都實現了,而且做了詳細的講解,下面就看下整個示例程式碼,方便理解。
package commonimport ( "errors" "io" "sync" "log")//一個安全的資源池,被管理的資源必須都實現io.Close介面type Pool struct { m sync.Mutex res chan io.Closer factory func() (io.Closer, error) closed bool}var ErrPoolClosed = errors.New("資源池已經被關閉。")//建立一個資源池func New(fn func() (io.Closer, error), size uint) (*Pool, error) { if size <= 0 { return nil, errors.New("size的值太小了。") } return &Pool{ factory: fn, res: make(chan io.Closer, size), }, nil}//從資源池裡獲取一個資源func (p *Pool) Acquire() (io.Closer,error) { select { case r,ok := <-p.res: log.Println("Acquire:共享資源") if !ok { return nil,ErrPoolClosed } return r,nil default: log.Println("Acquire:新生成資源") return p.factory() }} //關閉資源池,釋放資源func (p *Pool) Close() { p.m.Lock() defer p.m.Unlock() if p.closed { return } p.closed = true //關閉通道,不讓寫入了 close(p.res) //關閉通道里的資源 for r:=range p.res { r.Close() }}func (p *Pool) Release(r io.Closer){ //保證該操作和Close方法的操作是安全的 p.m.Lock() defer p.m.Unlock() //資源池都關閉了,就省這一個沒有釋放的資源了,釋放即可 if p.closed { r.Close() return } select { case p.res <- r: log.Println("資源釋放到池子裡了") default: log.Println("資源池滿了,釋放這個資源吧") r.Close() } }
好了,資源池管理寫好了,也知道資源池是如何實現的啦,現在我們看看如何使用這個資源池,模擬一個資料庫連線池吧。
package mainimport ( "flysnow.org/hello/common" "io" "log" "math/rand" "sync" "sync/atomic" "time")const ( //模擬的最大goroutine maxGoroutine = 5 //資源池的大小 poolRes = 2)func main() { //等待任務完成 var wg sync.WaitGroup wg.Add(maxGoroutine) p, err := common.New(createConnection, poolRes) if err != nil { log.Println(err) return } //模擬好幾個goroutine同時使用資源池查詢資料 for query := 0; query < maxGoroutine; query++ { go func(q int) { dbQuery(q, p) wg.Done() }(query) } wg.Wait() log.Println("開始關閉資源池") p.Close()}//模擬資料庫查詢func dbQuery(query int, pool *common.Pool) { conn, err := pool.Acquire() if err != nil { log.Println(err) return } defer pool.Release(conn) //模擬查詢 time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond) log.Printf("第%d個查詢,使用的是ID為%d的資料庫連線", query, conn.(*dbConnection).ID)}//資料庫連線type dbConnection struct { ID int32//連線的標誌}//實現io.Closer介面func (db *dbConnection) Close() error { log.Println("關閉連線", db.ID) return nil}var idCounter int32//生成資料庫連線的方法,以供資源池使用func createConnection() (io.Closer, error) { //併發安全,給資料庫連線生成唯一標誌 id := atomic.AddInt32(&idCounter, 1) return &dbConnection{id}, nil }
這時我們測試使用資源池的例子,首先定義了一個結構體dbConnection,它只有一個欄位,用來做唯一標記。然後dbConnection實現了io.Closer介面,這樣才可以使用我們的資源池。
createConnection函式對應的是資源池中的factory欄位,用來建立資料庫連線dbConnection的,同時為其賦予了一個為止的標誌。
接著我們就同時開了 5 個goroutine,模擬併發的資料庫查詢dbQuery,查詢方法裡,先從資源池獲取可用的資料庫連線,用完後再釋放。
這裡我們會建立 5 個資料庫連線,但是我們設定的資源池大小隻有 2 ,所以再釋放了 2 個連線後,後面的 3 個連線會因為資源池滿了而釋放不了,一會我們看下輸出的列印資訊就可以看到。
最後這個資源連線池使用完之後,我們要關閉資源池,使用資源池的Close方法即可。
2017/04/17 22:25:08 Acquire:新生成資源
2017/04/17 22:25:08 Acquire:新生成資源
2017/04/17 22:25:08 Acquire:新生成資源
2017/04/17 22:25:08 Acquire:新生成資源
2017/04/17 22:25:08 Acquire:新生成資源
2017/04/17 22:25:08 第2個查詢,使用的是ID為4的資料庫連線
2017/04/17 22:25:08 資源釋放到池子裡了
2017/04/17 22:25:08 第4個查詢,使用的是ID為1的資料庫連線
2017/04/17 22:25:08 資源釋放到池子裡了
2017/04/17 22:25:08 第3個查詢,使用的是ID為5的資料庫連線
2017/04/17 22:25:08 資源池滿了,釋放這個資源吧
2017/04/17 22:25:08 關閉連線 5
2017/04/17 22:25:09 第1個查詢,使用的是ID為3的資料庫連線
2017/04/17 22:25:09 資源池滿了,釋放這個資源吧
2017/04/17 22:25:09 關閉連線 3
2017/04/17 22:25:09 第0個查詢,使用的是ID為2的資料庫連線
2017/04/17 22:25:09 資源池滿了,釋放這個資源吧
2017/04/17 22:25:09 關閉連線 2
2017/04/17 22:25:09 開始關閉資源池
2017/04/17 22:25:09 關閉連線 4
2017/04/17 22:25:09 關閉連線 1
到這裡,我們已經完成了一個資源池的管理,並且進行了使用測試。
資源物件池的使用比較頻繁,因為我們想把一些物件快取起來,以便使用,這樣就會比較高效,而且不會經常呼叫GC,為此Go為我們提供了原生的資源池管理,防止我們重複造輪子,這就是sync.Pool,我們看下剛剛我們的例子,如果用sync.Pool實現。
package mainimport (
"log"
"math/rand"
"sync"
"sync/atomic"
"time")const (
//模擬的最大goroutine
maxGoroutine = 5)func main() {
//等待任務完成
var wg sync.WaitGroup
wg.Add(maxGoroutine)
p:=&sync.Pool{
New:createConnection,
}
//模擬好幾個goroutine同時使用資源池查詢資料
for query := 0; query < maxGoroutine; query++ {
go func(q int) {
dbQuery(q, p)
wg.Done()
}(query)
}
wg.Wait()}//模擬資料庫查詢
func dbQuery(query int, pool *sync.Pool) {
conn:=pool.Get().(*dbConnection)
defer pool.Put(conn)
//模擬查詢
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
log.Printf("第%d個查詢,使用的是ID為%d的資料庫連線", query, conn.ID)}//資料庫連線
type dbConnection struct {
ID int32//連線的標誌}//實現io.Closer介面
func (db *dbConnection) Close() error {
log.Println("關閉連線", db.ID)
return nil}var idCounter int32//生成資料庫連線的方法,以供資源池使用
func createConnection() interface{} {
//併發安全,給資料庫連線生成唯一標誌
id := atomic.AddInt32(&idCounter, 1)
return &dbConnection{ID:id}
}
進行微小的改變即可,因為系統庫沒有提供New這類的工廠函式,所以我們使用字面量建立了一個sync.Pool,注意裡面的New欄位,這是一個返回任意物件的方法,類似我們自己實現的資源池中的factory欄位,意思都是一樣的,都是當沒有可用資源的時候,生成一個。
這裡我們留意到系統的資源池是沒有大小限制的,也就是說預設情況下是無上限的,受記憶體大小限制。
資源的獲取和釋放對應的方法是Get和Put,也很簡潔,返回任意物件interface{}。
2017/04/17 22:42:43 第0個查詢,使用的是ID為2的資料庫連線
2017/04/17 22:42:43 第2個查詢,使用的是ID為5的資料庫連線
2017/04/17 22:42:43 第4個查詢,使用的是ID為1的資料庫連線
2017/04/17 22:42:44 第3個查詢,使用的是ID為4的資料庫連線
2017/04/17 22:42:44 第1個查詢,使用的是ID為3的資料庫連線
關於系統的資源池,我們需要注意的是它快取的物件都是臨時的,也就說下一次GC的時候,這些存放的物件都會被清除掉。
相關文章
- Go語言之併發示例-Pool(一)Go
- Go語言之併發示例(Runner)Go
- 深度解密 Go 語言之 sync.Pool解密Go
- 深入理解GO語言之併發機制Go
- Go語言之methodGo
- Go語言之介面Go
- Go語言之ContextGoContext
- Go語言之 Struct TagGoStruct
- go語言之反射-------ReflectionGo反射
- GO語言併發Go
- 深度解密 Go 語言之 channel解密Go
- 深度解密Go語言之 map解密Go
- 深度解密Go語言之Slice解密Go
- 深度解密Go語言之channel解密Go
- 深度解密GO語言之反射解密Go反射
- Go語言之讀寫鎖Go
- Go語言之包(package)管理GoPackage
- Go 併發程式設計 - 併發安全(二)Go程式設計
- 深度解密Go語言之context解密GoContext
- 深度解密 Go 語言之 context解密GoContext
- go語言之陣列與切片Go陣列
- Go語言之旅:基本型別Go型別
- Go語言之錯誤處理Go
- 第09章 Go語言併發,Golang併發Golang
- 《Go 語言併發之道》讀後感 - 第二章Go
- Golang語言之gRPC程式設計示例GolangRPCC程式程式設計
- Go高效併發 11 | 併發模式:Go 語言中即學即用的高效併發模式Go模式
- Go語言中的併發模式Go模式
- 深度解密 Go 語言之 sync.map解密Go
- go語言之結構體和方法Go結構體
- Go語言之於系統管理員Go
- Go語言併發程式設計Go程式設計
- 在 Fefora 上開啟 Go 語言之旅Go
- Go語言之變數逃逸(Escape Analysis)分析Go變數
- Go語言之陣列快速入門篇Go陣列
- Go語言之切片(slice)快速入門篇Go
- 二、Go語言基礎:go install與go fmt命令使用示例詳解Go
- 十九、Go語言基礎之併發Go