go語言多Goroutine共享數據庫連接的優雅關閉
在Go語言并發編程中,多個Goroutine共享數據庫連接時,如何安全地關閉連接是一個關鍵問題。不當的關閉方式可能導致數據丟失或程序崩潰。本文將探討幾種方案,并分析其優缺點。
假設我們有一個場景:多個Goroutine并發執行數據庫查詢操作,共享同一個數據庫連接。
錯誤示范:使用defer在主Goroutine關閉連接
以下代碼演示了一個常見的錯誤:
立即學習“go語言免費學習筆記(深入)”;
db := openDB() defer db.Close() // 錯誤:在主Goroutine關閉,其他Goroutine可能仍在使用 for i := 0; i < 10; i++ { go func(i int) { queryDB(db, i) }(i) } // ... 其他代碼 ...
defer db.Close() 會在主Goroutine結束時執行,但此時其他Goroutine可能仍在使用數據庫連接,導致程序崩潰或數據錯誤。
錯誤示范:在每個Goroutine中關閉連接
將db.Close()放在每個Goroutine中也不是正確的方案:
func queryDB(db *DB, i int) { defer db.Close() // 錯誤:每個Goroutine都嘗試關閉連接 // ... 數據庫查詢操作 ... }
這會導致連接被多次關閉,引發錯誤。
正確方案一:使用WaitGroup同步Goroutine
使用sync.WaitGroup可以確保所有Goroutine都完成工作后再關閉連接:
var wg sync.WaitGroup db := openDB() defer db.Close() // 正確:在所有Goroutine完成后關閉 for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { defer wg.Done() queryDB(db, i) }(i) } wg.Wait() // 等待所有Goroutine完成
此方法通過wg.Add(1)和wg.Done()來計數Goroutine,wg.Wait()阻塞直到所有Goroutine完成,確保連接在安全的時間點關閉。
正確方案二:使用全局變量和主Goroutine控制
另一種更簡潔的方法是將數據庫連接定義為全局變量,并在主Goroutine中統一管理:
var db *DB func main() { db = openDB() defer db.Close() // 正確:在主Goroutine統一關閉 // ... 啟動Goroutine ... } func queryDB(i int) { // ... 數據庫查詢操作 ... }
這種方式清晰地將連接的管理集中在主Goroutine,避免了并發訪問和關閉的問題。
選擇哪種方案取決于具體應用場景和代碼復雜度。WaitGroup方法適用于更復雜的并發場景,而全局變量方法在簡單場景下更簡潔易懂。 關鍵在于確保數據庫連接只被關閉一次,并且在所有Goroutine完成操作之后關閉。