本文共 1671 字,大约阅读时间需要 5 分钟。
map 引发的血案最近业务中,同事使用 map 来接收返回的结果,使用 WaitGroup 来并发处理执行返回的结果,结果上线之后,直接崩了。
日志显示大量的数据库缓存池连接失败:
{"ecode":-500,"message":"timed out while checking out a connection from connection pool"}{"ecode":-500,"message":"connection(xxxxxxxxx:xxxxx) failed to write: context deadline exceeded"} 来看伪代码:
package mainimport ( "fmt" "sync" "time")var count = 300func main() { var data = make(map[int]string, count) var wg sync.WaitGroup for i := 0; i < count; i++ { wg.Add(1) go func(i int) { defer wg.Done() time.Sleep(time.Second * 1) mockSqlPool() data[i] = "test" }(i) } fmt.Println("-----------WaitGroup执行结束了-----------") wg.Wait()}// 模拟数据库的连接和释放func mockSqlPool() { defer fmt.Println("关闭pool") fmt.Println("我是pool")} 运行的输出显示:
...我是pool关闭poolconcurrent map writes我是poolgoroutine 56 [running]: runtime.throw(...)...
map 不是并发安全
map 在并发环境下不是安全的,特别是在多个 goroutine 同时修改时,会导致 panic。 在示例中,多个 goroutine 并发写入 map,导致并发修改,触发 concurrent map writes 错误。 避免在循环中连接数据库
在循环中频繁连接数据库会导致连接泄漏,进而导致数据库连接池连接无法被及时释放。WaitGroup 的信号量WaitGroup 使用信号量机制来管理 goroutine 的等待状态:
Wait() 会阻塞,直到所有 goroutine 完成。Done() 会在最后一个 goroutine 完成时,释放信号量,允许 Wait() 继续执行。输出显示有多个 goroutine 处于 semacquire 状态,这意味着它们正在等待信号量被唤醒。然而,由于 WaitGroup 因 panic 退出,无法通过 Done() 通知 goroutine 退出,导致这些 goroutine 阻塞到最后,占用数据库连接,造成连接泄漏。
使用 sync.RwLock 或 sync.Mutex 保护 map
map 的读写操作是安全的。 避免在 goroutine 中直接访问数据库连接
使用数据库连接池(如sqlc 或自定义连接池)来管理数据库连接,确保连接能被及时释放。 优化 WaitGroup 使用
WaitGroup 中正确处理 Done() 信号,避免 panic 导致的资源泄漏。 通过以上优化,可以避免并发写入导致的 panic,同时确保数据库连接能够被正确释放,避免连接泄漏问题。
转载地址:http://xzcfz.baihongyu.com/