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完成操作之后关闭。