这里或许是互联网从业者的最后一片净土,随客社区期待您的加入!
您需要 登录 才可以下载或查看,没有账号?立即注册
×
本帖最后由 mrkong 于 2025-9-24 14:31 编辑
在 Go 语言的世界里,goroutine 是最亮眼的特性之一。轻量级、高并发、成本低——这几乎成了 Go 成为云原生时代“标配语言”的最大资本。
但与此同时,goroutine 泄漏(Goroutine Leak) 也在悄无声息中埋下隐患,它往往不易被察觉,却能在关键时刻将系统拖入崩溃的深渊。 今天,我们就从原理、案例、排查、预防等几个角度,深挖这一“隐蔽却致命”的性能杀手。
一、什么是 Goroutine 泄漏?简单来说,goroutine 泄漏就是指那些已经不再需要,却依旧存活在内存中的 goroutine。它们通常因为阻塞或缺乏退出机制,无法被回收,最终像僵尸一样堆积。 特点: 二、常见的 Goroutine 泄漏场景- func worker(ch <-chan int) {
- for v := range ch {
- fmt.Println(v)
- }
- }
- func main() {
- ch := make(chan int)
- go worker(ch)
- // 忘记 close(ch),worker 一直阻塞
- }
复制代码 没有关闭 channel,goroutine 永远等数据。
2. select 中缺少退出分支
- func doWork(ch <-chan int) {
- for {
- select {
- case v := <-ch:
- fmt.Println(v)
- // 没有退出条件
- }
- }
- }
复制代码 goroutine 永远活着。
3. context 没有传递 / 取消 - func fetch(ctx context.Context, url string) {
- req, _ := http.NewRequest("GET", url, nil)
- // 没有用 ctx 绑定请求,超时无法中断
- http.DefaultClient.Do(req)
- }
复制代码 网络抖动时,goroutine 卡死在等待。
4. timer / ticker 忘记 stop - func main() {
- ticker := time.NewTicker(time.Second)
- go func() {
- for range ticker.C {
- fmt.Println("tick")
- }
- }()
- // ticker 没有 stop,goroutine 永远运行
- }
复制代码
三、真实事故案例在某大型金融 SaaS 平台的支付服务中,研发团队发现服务每周都会出现一次 内存飙升。经过排查,原因是: 结果: 内存占用飙升 8 倍; 部分节点 CPU 飙到 100%; 最终触发自动扩容,成本翻倍。
修复措施仅仅是:用 context.WithTimeout 控制 goroutine 生命周期,并确保退出路径。
四、如何排查 Goroutine 泄漏?- go tool pprof http://localhost:6060/debug/pprof/goroutine
复制代码 2. 运行时采样 - import "runtime"
- fmt.Println("当前 goroutine 数量:", runtime.NumGoroutine())
复制代码 3. goleak 库 - import "go.uber.org/goleak"
- func TestMain(m *testing.M) {
- goleak.VerifyTestMain(m)
- }
复制代码在单元测试中自动检测是否存在泄漏。 五、预防 Goroutine 泄漏的最佳实践
使用 context 管理生命周期 始终关闭 channel select 中增加退出分支 - select {
- case <-ctx.Done():
- return
- }
复制代码及时 stop timer / ticker CI 中引入 goleak |