go語言多goroutine共享數(shù)據(jù)庫連接及優(yōu)雅關(guān)閉的最佳實(shí)踐
在Go語言中,多個goroutine共享數(shù)據(jù)庫連接并確保正確關(guān)閉是并發(fā)編程中的常見挑戰(zhàn)。本文將分析一個新手常見的錯誤示例,并提供最佳解決方案。
新手通常會嘗試使用defer db.Close()來關(guān)閉數(shù)據(jù)庫連接,但這種方法在多goroutine場景下無效。defer語句僅在當(dāng)前goroutine結(jié)束時執(zhí)行,無法保證所有g(shù)oroutine結(jié)束后再關(guān)閉連接。將defer db.Close()放在共享數(shù)據(jù)庫連接的函數(shù)內(nèi)部同樣無效,因?yàn)檫@會導(dǎo)致多個goroutine嘗試同時關(guān)閉同一個連接,引發(fā)錯誤。
一些新手嘗試使用sync.WaitGroup來協(xié)調(diào)goroutine的執(zhí)行,并在所有g(shù)oroutine完成后關(guān)閉連接。雖然可行,但代碼較為復(fù)雜。
更優(yōu)雅的解決方案:使用連接池
立即學(xué)習(xí)“go語言免費(fèi)學(xué)習(xí)筆記(深入)”;
為了解決這個問題,并提高數(shù)據(jù)庫連接的利用率,建議使用連接池。連接池預(yù)先創(chuàng)建一定數(shù)量的數(shù)據(jù)庫連接,goroutine從池中獲取連接使用,用完后歸還到池中。當(dāng)程序退出時,關(guān)閉連接池即可釋放所有連接。
以下是一個使用連接池的示例:
package main import ( "database/sql" "fmt" "sync" _ "github.com/go-sql-driver/mysql" // 替換成你的數(shù)據(jù)庫驅(qū)動 ) type dbConn struct { conn *sql.DB mu sync.Mutex } func (dc *dbConn) GetConn() (*sql.DB, error) { dc.mu.Lock() defer dc.mu.Unlock() return dc.conn, nil } func (dc *dbConn) Close() error { dc.mu.Lock() defer dc.mu.Unlock() return dc.conn.Close() } func querydb(dc *dbConn, i int) { conn, err := dc.GetConn() if err != nil { fmt.Printf("Error getting connection: %vn", err) return } defer conn.Close() // 這里關(guān)閉的是從池中獲取的連接,而不是池本身 // ... 數(shù)據(jù)庫操作 ... } func main() { db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/database") if err != nil { panic(err) } defer db.Close() // 這里關(guān)閉的是連接池 dbConn := &dbConn{conn: db} var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { defer wg.Done() querydb(dbConn, i) }(i) } wg.Wait() }
這個示例中,dbConn 結(jié)構(gòu)體管理數(shù)據(jù)庫連接,GetConn 方法從連接池獲取連接,Close 方法關(guān)閉連接池。 querydb 函數(shù)從連接池獲取連接,使用后歸還。主函數(shù)負(fù)責(zé)創(chuàng)建和關(guān)閉連接池。sync.WaitGroup 用于等待所有g(shù)oroutine完成。 請記得替換成你的數(shù)據(jù)庫驅(qū)動和連接字符串。
使用連接池是處理Go語言多goroutine共享數(shù)據(jù)庫連接并確保正確關(guān)閉的最佳實(shí)踐,它不僅保證了連接的正確關(guān)閉,也提高了連接的復(fù)用率,避免了頻繁創(chuàng)建和銷毀連接的開銷。