goroutine 不能靠 time.sleep 等待完成,因 main 退出会强制终止所有 goroutine;正确方式是用 sync.waitgroup 配对 add/wait/done,或 context 控制生命周期,避免泄漏和数据丢失。

goroutine 不能靠 time.Sleep 等着它跑完,main 函数一退出,所有 goroutine 立刻被杀掉——这是最常踩的坑,也是绝大多数“没输出”“日志断了”“数据没写入”的根本原因。
为什么 time.Sleep 不是同步手段
它只是碰运气:时长难估、不可靠、掩盖真实依赖。你加 time.Sleep(1 * time.Second),结果网络慢一点、磁盘卡一下,协程就又被截断;加太长又拖慢开发反馈。更关键的是,它完全无法表达“等这个任务做完”这个语义。
- main 函数返回 → 运行时强制终止所有 goroutine,不管它们是否在
fmt.Println、http.Post还是db.Exec -
time.Sleep无法感知任务是否真正完成,只管“等够时间” - 在测试或 CI 环境中,调度延迟波动大,
time.Sleep更容易失效
sync.WaitGroup 怎么用才不出错
它是等待一组 goroutine 完成的最直接方式,但必须配对使用,漏掉 wg.Add 或 wg.Done 都会卡死或 panic。
- 启动前调用
wg.Add(n),n 是你要等的 goroutine 数量(不是循环次数) - 每个 goroutine 结尾必须有
defer wg.Done(),否则wg.Wait()永远不返回 - 不要在循环里反复
wg.Add(1)后直接go f(),除非你确保每次调用都对应一个Done() - 示例中常见错误:
for _, v := range items { wg.Add(1); go process(v) }→ 必须把wg.Done()放进 goroutine 内部,且注意变量捕获问题
循环中启动 goroutine 为什么总拿到同一个值
Go 的 for 循环变量 v 是复用的,所有 goroutine 共享同一块内存地址。等它们真正执行时,v 早已是循环末尾的值。
- 错误写法:
for _, v := range items { go func() { fmt.Println(v) }() }→ 所有输出都是最后一个v - 正确写法一(传参):
for _, v := range items { go func(val string) { fmt.Println(val) }(v) } - 正确写法二(闭包捕获):
for _, v := range items { v := v; go func() { fmt.Println(v) }() } - 如果函数本身接受参数,优先用第一种——语义清晰、不易出错
goroutine 泄漏怎么发现和防住
泄漏不是“没写完”,而是“永远等不到结束”。典型表现:进程常驻、runtime.NumGoroutine() 持续上涨、内存缓慢增长。
- 无限等待 channel:比如
data := ,但没人 close 或 send,goroutine 就卡死在那 - HTTP handler 或定时任务没接
context.Context,无法响应超时或取消信号 - worker 模式下,任务通道没关闭,
for range ch永远不会退出 - 排查建议:定期打印
runtime.NumGoroutine();用pprof查 goroutine stack;关键路径强制加 context 超时
真正麻烦的从来不是“怎么开 goroutine”,而是“它什么时候停、谁来通知它停、停之前有没有清理好资源”。别让协程变成幽灵——看不见,但吃内存、占连接、拖垮整个服务。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











