为了避免 goroutine 泄漏,go 框架提供了以下机制:使用上下文(context)取消未使用的 goroutine;使用 goroutine 组跟踪 goroutine 并通知完成;使用管道发送取消信号。实战案例中,上下文用于在取消时退出 goroutine,goroutine 组用于跟踪 goroutine 的完成,管道用于发送取消信号。
避免 Golang 框架中 Goroutine 泄漏的技巧
Goroutine 泄漏是一个常见的 Go 应用程序问题,它会导致内存浪费和性能下降。为了避免这种情况,Golang 框架提供了多种机制:
1. 使用上下文(Context)
立即学习“go语言免费学习笔记(深入)”;
上下文可以用来取消未使用的 Goroutine。使用 context.WithCancel() 创建一个新的上下文,并传递给 Goroutine 函数:
ctx, cancel := context.WithCancel(context.Background())
go func() {
for {
select {
case <-ctx.Done():
return
default:
// 执行 Goroutine 任务
}
}
}()
// ...
cancel() // 取消 Goroutine
2. 使用 Goroutine 组
Goroutine 组可以跟踪一组 Goroutine,并在它们全部完成时通知。使用 sync.WaitGroup 创建一个新的 Goroutine 组,并传递给 Goroutine 函数:
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
// 执行 Goroutine 任务
wg.Done()
}()
}
// ...
wg.Wait() // 等待所有 Goroutine 完成
3. 使用管道(Channel)
管道可以用来向 Goroutine 发送一个取消信号,关闭管道将通知 Goroutine 退出:
done := make(chan struct{})
go func() {
for {
select {
case <-done:
return
default:
// 执行 Goroutine 任务
}
}
}()
// ...
close(done) // 发送取消信号
实战案例
示例 1: 使用上下文
package main
import (
"context"
"fmt"
"sync"
"time"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
wg.Add(1)
go longRunningGoroutine(ctx, &wg)
time.Sleep(5 * time.Second)
cancel()
wg.Wait()
}
func longRunningGoroutine(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
fmt.Println("Goroutine has been cancelled")
return
default:
fmt.Println("Goroutine is running")
time.Sleep(1 * time.Second)
}
}
}
在上面的示例中,longRunningGoroutine() 在一个单独的 Goroutine 中运行,它每秒向控制台打印Goroutine is running。当用户按下任意键时,主 Goroutine 取消上下文的上下文的Done()通道,这会通知longRunningGoroutine() 退出。
示例 2: 使用 Goroutine 组
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
fmt.Println("Goroutine", i)
wg.Done()
}(i)
}
wg.Wait()
}
在上面的示例中,主 Goroutine 使用一个 Goroutine 组来跟踪 10 个 Goroutine。每个 Goroutine打印其 ID,然后通知 Goroutine 组它已完成。主 Goroutine等待所有 Goroutine 完成,然后退出。
示例 3: 使用管道
package main
import (
"fmt"
"sync"
"time"
)
func main() {
done := make(chan struct{})
var wg sync.WaitGroup
wg.Add(1)
go longRunningGoroutine(done, &wg)
time.Sleep(5 * time.Second)
close(done)
wg.Wait()
}
func longRunningGoroutine(done <-chan struct{}, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-done:
fmt.Println("Goroutine has been cancelled")
return
default:
fmt.Println("Goroutine is running")
time.Sleep(1 * time.Second)
}
}
}
在上面的示例中,longRunningGoroutine() 在一个单独的 Goroutine 中运行,它每秒向控制台打印Goroutine is running。当用户按下任意键时,主 Goroutine 关闭done通道,这将通知longRunningGoroutine() 退出。