可能导致资源泄漏的场景
- goroutine未正确退出:
- 场景:假设在一个函数中,使用
WaitGroup
启动多个goroutine
执行任务。如果某个goroutine
在执行过程中遇到错误,没有正确处理并退出,而是进入一个无限循环,那么这个goroutine
将一直运行,造成资源泄漏。例如:
package main
import (
"fmt"
"sync"
)
func worker(wg *sync.WaitGroup) {
defer wg.Done()
// 模拟一个可能出错的任务
if true {
for {
// 这里应该是处理错误并退出,但是进入了无限循环
}
}
fmt.Println("Worker finished")
}
func main() {
var wg sync.WaitGroup
wg.Add(1)
go worker(&wg)
wg.Wait()
fmt.Println("All workers finished")
}
- 分析:上述代码中,
worker
函数在遇到特定条件(这里简单假设为true
)时,进入无限循环,没有调用wg.Done()
,导致main
函数中的wg.Wait()
永远不会返回,同时这个goroutine
也不会结束,浪费系统资源。
- 资源未释放:
- 场景:当
goroutine
中使用了外部资源,如文件描述符、数据库连接等,在goroutine
异常结束时,如果没有正确释放这些资源,就会造成资源泄漏。例如:
package main
import (
"database/sql"
"fmt"
"sync"
_ "github.com/go - sql - driver/mysql"
)
func databaseWorker(wg *sync.WaitGroup, db *sql.DB) {
defer wg.Done()
// 从数据库连接池获取连接
conn, err := db.Conn(nil)
if err!= nil {
// 这里没有关闭连接就返回,导致连接泄漏
return
}
// 这里假设处理数据库操作
// 没有正确关闭连接
}
func main() {
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/test")
if err!= nil {
panic(err)
}
defer db.Close()
var wg sync.WaitGroup
wg.Add(1)
go databaseWorker(&wg, db)
wg.Wait()
fmt.Println("All workers finished")
}
- 分析:在
databaseWorker
函数中,获取数据库连接后,如果发生错误,没有关闭连接就返回,这会导致数据库连接一直占用,造成资源泄漏。
避免资源泄漏的方法
- 使用Context管理goroutine生命周期:
package main
import (
"context"
"fmt"
"sync"
"time"
)
func worker(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
default:
// 执行任务
fmt.Println("Working...")
time.Sleep(time.Second)
}
}
}
func main() {
var wg sync.WaitGroup
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
wg.Add(1)
go worker(ctx, &wg)
wg.Wait()
fmt.Println("All workers finished")
}
- 解释:通过
context.WithTimeout
创建一个带有超时的Context
,在worker
函数中使用select
监听ctx.Done()
通道。当Context
超时或者被取消时,ctx.Done()
通道会被关闭,worker
函数可以及时退出,避免goroutine
无限运行。
- 正确处理错误并释放资源:
package main
import (
"database/sql"
"fmt"
"sync"
_ "github.com/go - sql - driver/mysql"
)
func databaseWorker(wg *sync.WaitGroup, db *sql.DB) {
defer wg.Done()
// 从数据库连接池获取连接
conn, err := db.Conn(nil)
if err!= nil {
// 错误处理并关闭连接
fmt.Println("Error getting connection:", err)
return
}
defer conn.Close()
// 这里假设处理数据库操作
}
func main() {
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/test")
if err!= nil {
panic(err)
}
defer db.Close()
var wg sync.WaitGroup
wg.Add(1)
go databaseWorker(&wg, db)
wg.Wait()
fmt.Println("All workers finished")
}
- 解释:在
databaseWorker
函数中,获取数据库连接后,使用defer
语句确保无论是否发生错误,连接都会被正确关闭。这样可以避免资源泄漏。同时,在错误处理部分,可以根据实际情况进行日志记录等操作。
- 结合WaitGroup和Context:
package main
import (
"context"
"fmt"
"sync"
"time"
)
func complexWorker(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
default:
// 执行复杂任务,例如调用其他可能产生资源的函数
fmt.Println("Doing complex work...")
time.Sleep(time.Second)
}
}
}
func main() {
var wg sync.WaitGroup
ctx, cancel := context.WithCancel(context.Background())
for i := 0; i < 3; i++ {
wg.Add(1)
go complexWorker(ctx, &wg)
}
// 模拟一段时间后取消任务
time.Sleep(2 * time.Second)
cancel()
wg.Wait()
fmt.Println("All workers finished")
}
- 解释:这里通过
context.WithCancel
创建一个可取消的Context
,启动多个goroutine
执行复杂任务。在每个goroutine
中监听ctx.Done()
通道以实现及时退出。同时使用WaitGroup
确保所有goroutine
完成后再继续执行后续代码,避免资源泄漏。