goroutine 必须用 context 控制,否则必然泄漏;需以 ctx 为第一参数、监听取消信号、阻塞操作进 select、第三方调用确认支持 ctx。

Go 函数里启动 goroutine,不加 context 控制或没设退出路径,99% 会泄漏——不是“可能”,是“必然”。 它不会 panic,也不报错,只悄悄吃内存、涨 runtime.NumGoroutine(),直到服务变慢或 OOM。
goroutine 启动前必须带 context 参数
裸写 go func() { ... }() 是高危操作。只要函数体里有阻塞点(time.Sleep、http.Do、ch <-、<-ch),就必须接收 context.Context 并在内部监听取消信号。
- ctx 必须作为第一个参数传入,避免闭包意外捕获外层已 cancel 的 ctx
- 不要依赖 defer cancel() 就以为万事大吉——cancel() 调用必须发生在 goroutine 启动的作用域内(比如 handler 函数末尾)
- 第三方库调用要查清是否真响应 ctx:比如
db.Query不响应,得换db.QueryContext(ctx, ...) - HTTP 请求必须用
req.WithContext(ctx)+client.Do(req),否则 DNS 卡住就永远挂
所有阻塞操作必须进 select,并把
漏掉这一条,等于没传 ctx。select 不是装饰,是强制退出开关。
-
select里不能只有case <-ch:或case <-time.After(...):,必须有case <-ctx.Done(): return -
<-ctx.Done()必须放在第一个 case,否则其他分支先就绪时会跳过取消检查 - 别用
time.After替代time.NewTicker做轮询——time.After创建的 timer 不可 Stop,超时前一直占资源 - 正确轮询模式:
ticker := time.NewTicker(1 * time.Second); defer ticker.Stop(); for { select { case <-ctx.Done(): return; case <-ticker.C: doWork() } }
channel 发送/接收必须配超时或缓冲,且关闭责任要清晰
无缓冲 channel 单向使用、receiver 提前退出、sender 不关 channel,三者任一都会卡死 goroutine。
- 发送前必须用
select包一层,至少带default或<-time.After(...)分支防永久阻塞 - 谁创建 channel,谁负责
close();receiver 别 assume sender 会关——for range ch 只在 channel 关闭后退出 - 接收方要用
v, ok := <-ch检查是否已关闭,不能只靠for range就完事 - 慎用
len(ch) == 0判断可读性——不可靠,且无法解决阻塞问题 - 全局缓存里存子切片(如
cache["key"] = bigData[:10])会锁住整个底层数组,不是 goroutine 泄漏,但效果类似
defer 在 goroutine 里基本失效,资源清理必须显式做
goroutine 卡在死循环或阻塞点时,defer 根本不执行。文件句柄、HTTP body、DB rows、ticker 都因此持续累积。
-
defer resp.Body.Close()只在函数 return 时触发;如果http.Do卡在握手阶段,resp 都没拿到,Close 就 never 执行 -
time.Ticker和time.Timer必须显式ticker.Stop(),哪怕你写了select监听 ctx —— 两者不互斥,缺一不可 -
sync.WaitGroup不防泄漏:它只管“你声明等几个”,不管“它们退没退”。WG.Add 后没对应 Done,或 Done 被 panic 跳过,都会导致等待永远不结束 - 测试阶段就要拦截:用
uber-go/goleak在TestMain里加goleak.VerifyNone(m),比线上报警早十倍发现问题
真正难处理的从来不是“怎么写对”,而是“哪一层漏了”。一个 HTTP handler 传了 ctx,但里面调的 service 层用了裸 db.Query,或 goroutine 里开了 time.AfterFunc 又捕获了大 struct,泄漏就藏在这些缝隙里。pprof 的 /debug/pprof/goroutine?debug=2 输出里,凡是大量停在 chan receive、semacquire、IO wait 的,基本就是这些地方没对齐。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等

















