
Go中“goroutine内存泄漏”本质是goroutine卡在select等阻塞操作而永不退出并非堆内存未被GC回收。goroutine 永远不退出不是内存没释放是它卡住了Go 里所谓“goroutine 内存泄漏”本质不是堆内存没被 GC 回收而是 goroutine 启动后卡在 select、、codetime.Sleep 或 http.Get 上永远不返回。它持续占着栈至少 2KB、调度器跟踪开销、以及闭包捕获的所有变量——这些对象全不能被 GC。典型现象程序运行越久runtime.NumGoroutine() 只增不减pprof 查 /debug/pprof/goroutine?debug2 显示大量 goroutine 停在 chan receive、semacquire 或 select最常见场景后台轮询、HTTP handler 中启 goroutine 处理异步任务、worker pool 的 worker 从 channel 取任务但没退出逻辑别信“defer 能兜底”如果 goroutine 卡在死循环或永久阻塞里defer 根本不会执行所有阻塞操作必须配合 ctx.Done() 做 select不是加了 context.Context 就安全关键是要让 goroutine 主动响应取消信号。裸调 time.Sleep(1 * time.Second) 或直接 ch 都是高危操作。错误写法go func() { for { doWork(); time.Sleep(1 * time.Second) } }() —— 一旦 ctx 被 cancel它照样睡下去正确写法把阻塞操作放进 select且必含 分支示例go func(ctx context.Context) { ticker : time.NewTicker(1 * time.Second) defer ticker.Stop() // 必须显式 stop for { select { case -ticker.C: doWork() case -ctx.Done(): log.Println(shutting down:, ctx.Err()) return } }}(ctx)第三方库也要看是否真正支持 context比如数据库要用 QueryContext而不是 QueryHTTP 客户端要用 Do(req.WithContext(ctx))channel 发送/接收必须有兜底不能只靠“有人会收”向无缓冲 channel 发送数据若无人接收goroutine 立刻阻塞有缓冲 channel 若已满同样阻塞。这不是设计缺陷是使用错误——你得明确谁负责消费、何时关闭、怎么避免卡死。发送方不能假设 receiver 一定存在或永不退出receiver 也不能假设 sender 一定会 close channel避免裸写 ch 改用带 codedefault 或超时的 select示例防阻塞发送select {case ch - data: // 成功default: log.Println(channel full, dropping)}示例可靠接收for { select { case job, ok : -jobs: if !ok { return // channel 已关闭 } handle(job) case -ctx.Done(): return }}记得sender 关闭 channelreceiver 用 ok 判断永远不要向已关闭的 channel 发送会 panictimer/ticker 和 HTTP handler 是泄漏重灾区很多人以为 time.NewTicker 或 handler 里启个 goroutine 是“轻量操作”但它们生命周期失控时比普通 goroutine 更难察觉。 Tellers AI Tellers是一款自动视频编辑工具可以将文本、文章或故事转换为视频。