尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

GO练习题-Goroutinue泄漏

GO练习题-Goroutinue泄漏
📅 发布时间:2026/6/30 9:16:35

某电商公司的订单服务,上线后运行了大约 2 小时,OOM(Out of Memory)被 K8s 杀死,进程反复重启。

运维同事看到的日志只有一行:fatal error: runtime: out of memory。

开发同事重启了几次,问题依旧。直到有经验的架构师用pprof看了一眼 goroutine 分布,才发现:每处理一个请求,就 leak 一个 goroutine,而且这些 goroutine 全部卡在 channel 的 send 操作上,永远无法退出。

1000 个 QPS × 2 小时 = 720 万次泄露的 goroutine,每个 goroutine 默认 2KB 栈空间,再加上分配的堆对象,最终把内存吃光了。

教训:goroutine 泄露是 Go 生产环境中最隐蔽、危害最大的并发问题之一。没有之一。


学习目标

  • 理解 goroutine 的生命周期:谁启动、谁负责停止
  • 理解 channel 阻塞如何导致 goroutine 泄露
  • 掌握context.WithCancel的正确用法和取消传播链
  • 学会用net/http/pprof检测 goroutine 泄露

问题描述

下面的代码是一个模拟的请求处理函数。每个请求启动一个 worker goroutine 来处理任务,并将结果发送到 channel。

但这段代码有一个 goroutine 泄露——worker goroutine 在完成工作后,试图向resultCh发送结果时,如果没有人读取,就会永远阻塞。

请找出泄露点并修复它。

原始代码(有 BUG)

package main import ( "fmt" "math/rand" "time" ) func simulateRequest() { resultCh := make(chan string) // 无缓冲! // 启动 worker goroutine 处理"请求" go func() { // 模拟耗时处理(300~800ms) time.Sleep(time.Duration(rand.Intn(500)+300) * time.Millisecond) resultCh <- fmt.Sprintf("处理完成,耗时 %dms", 300+rand.Intn(500)) // TODO: 这里会泄露——如果 resultCh 没人读,goroutine 永远卡住 }() // TODO: 模拟请求取消或超时的情况(比如 500ms 后调用方不再关心结果) // 如果 worker 还没完成,它就 leak 了 time.Sleep(500 * time.Millisecond) // 这里不会收到结果,因为 goroutine 已经被阻塞在 send 上了 fmt.Println("请求方放弃等待") } func main() { for i := 0; i < 5; i++ { simulateRequest() fmt.Printf("第 %d 个请求处理完毕\n\n", i+1) time.Sleep(200 * time.Millisecond) } fmt.Println("所有请求处理完毕") // 运行后观察:goroutine 数量只增不减 time.Sleep(2 * time.Second) }

预期现象:运行后 goroutine 数量持续增长,因为这些 goroutine 卡在resultCh <- ...上无法退出。


修复要求

使用context.WithCancel修复泄露问题,确保:

  1. worker goroutine 能感知到取消信号,及时退出
  2. 使用defer cancel()确保清理
  3. 修复后运行,goroutine 数量保持稳定

面试追问

Q1: 如何用 pprof 检测 goroutine 泄露?

答:

// 在 HTTP 服务中启用 pprof import _ "net/http/pprof"

然后通过以下命令检查:

# 查看 goroutine 数量 curl http://localhost:6060/debug/pprof/goroutine?debug=1 # 导出 goroutine profile 到文件 curl http://localhost:6060/debug/pprof/goroutine -o goroutine.prof # 用 pprof 工具分析 go tool pprof goroutine.prof

生产环境中,定期采集 goroutine 数量并设置告警是必要的。


Q2: channel 阻塞的 4 种情况?

#场景谁阻塞是否泄露
1sendCh <- val,无人接收发送方是,发送方 goroutine 永久阻塞
2<-recvCh,无人发送接收方是,接收方 goroutine 永久阻塞
3双向 channel,两端都关闭后继续读写读写方是,panic 或永久阻塞
4select 中没有 default 分支,所有 case 都阻塞select 所在 goroutine是,goroutine 永久阻塞

Q3: 如何用 context 预防 goroutine 泄露?

答:核心原则——所有从请求衍生出的 goroutine,都应该传入同一个 context,并在 context 取消时优雅退出。

func handler(ctx context.Context) { ctx, cancel := context.WithCancel(ctx) defer cancel() // 关键:无论什么路径退出,都调用 cancel go func() { defer fmt.Println("goroutine 已退出") select { case <-ctx.Done(): return // context 取消,优雅退出 case resultCh <- doWork(): // 发送成功,正常退出 return } }() // ... 处理逻辑 }

context 取消传播链:

用户请求取消 / 超时 → handler 的 context 被 cancel → 所有通过 context.WithCancel/WithTimeout 衍生出的子 context 被 cancel → 所有 select 中 case <-ctx.Done() 分支触发 → 所有衍生 goroutine 退出

相关新闻

  • 从SDH到OTN:一张图看懂光传送网的演进与核心架构
  • ChatGPT o1推理模型:为什么你的vLLM集群吞吐暴跌?揭秘o1专属tokenization预处理冲突及4步热修复方案
  • MSP430中断控制器与FRAM控制器深度解析:从寄存器配置到实战优化

最新新闻

  • 从 Hello World 到生产服务,vLLM 在 AMD 平台的落地路径
  • Splunk高危漏洞CVE-2026-20163深度剖析与紧急处置指南
  • DamaiHelper技术深度解析:Python+Selenium如何实现300%抢票效率提升
  • 从Litz线选型到线圈实测:构建高效无线耦合系统的关键步骤
  • 2026年AI论文平台实测:5款神器从选题到格式全流程护航
  • Java CRUD自动生成怎么最快?AI读懂项目上下文是关键

日新闻

  • 【计算机毕业设计案例】基于 Spring Boot+Vue 的电影售票系统设计与实现 前后端分离架构下影院在线购票管理平台(程序+文档+讲解+定制)
  • 到底 TMD 用哪个: npm, pnpm, Yarn, Bun, Deno? 傻瓜, 当然用 npm 啦
  • Google限制Meta使用Gemini模型 凸显AI授权竞争白热化

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号