必须用 sync.waitgroup 显式计数并传指针,defer wg.done() 置于 goroutine 入口顶部;避免闭包变量捕获错误;高并发需 channel + 固定 worker 池限流,任务通道须设合理缓冲。

用 sync.WaitGroup 控制批处理完成时机
直接起一堆 go 函数跑任务,主流程不知道啥时候结束,容易提前返回或 panic。必须用 sync.WaitGroup 显式计数。
- 调用
wg.Add(n)前必须确定总任务数,不能在循环里动态加——否则可能漏计或重复加 -
defer wg.Done()要放在 goroutine 入口函数最顶部,确保无论正常 return 还是 panic 都能减一 - 别把
wg传值进去:写成go f(&wg),传指针;传值会导致每个 goroutine 操作副本,Wait()永远不返回 - 闭包陷阱常见:
for _, id := range ids { go f(id) }是对的;go f(id)写成go f(ids[i])且没做变量捕获,所有 goroutine 会读到同一个id最终值
用 channel + worker pool 限流防爆
几千个任务全开 goroutine,瞬间吃光内存、压垮数据库连接池、触发 runtime: goroutine stack exceeds 1000000000-byte limit。得用固定 worker 数消费队列。
- 任务通道要带缓冲:
jobs := make(chan Job, 100),容量按峰值吞吐 ×1.5 预估,太小卡生产者,太大等于没控流 - 启动 worker 时用
for i := 0; i ,别写成 <code>go func(){...}()闭包捕获i导致全为最后一个值 - 发任务要用
select { case jobs ,非阻塞写入,避免上游被队列堵死 - 务必
close(jobs),否则 worker 的for job := range jobs会永远阻塞,WaitGroup等不到结束
用 errgroup.Group 统一错误与取消
批处理里一个任务失败,你得立刻停掉其余还在跑的——比如上传文件中途鉴权失败,不该继续发后续请求。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
eg, _ := errgroup.WithContext(ctx)比手写sync.WaitGroup+ 全局error变量更可靠:任一子 goroutine 返回非 nil error,eg.Wait()就立即返回,且其余 goroutine 收到 context cancel 信号 - 别忘了给每个外部调用套
ctx:HTTP 请求用http.NewRequestWithContext(ctx, ...),DB 查询用db.QueryContext(ctx, ...) - 如果任务本身不支持 context(比如纯计算),需在循环里手动检查
if ctx.Err() != nil { return ctx.Err() }
注意 sync.Pool 复用和 GC 压力
高频创建任务结构体(如每秒万级日志解析),GC 会频繁触发,STW 时间变长。
- 定义
var taskPool = sync.Pool{New: func() interface{} { return &Task{} }},获取用t := taskPool.Get().(*Task),用完立刻taskPool.Put(t) - Pool 不保证复用,也不清空内容,每次
Get()后必须重置字段,不能依赖“上次用完还留着值” - 别在 HTTP handler 里长期 hold 住从 Pool 拿的对象——goroutine 生命周期短,但对象可能被其他 goroutine 复用,造成数据污染
真正卡住高并发批处理的,往往不是并发度不够,而是没关掉超时、没设连接池上限、没控制 channel 缓冲大小,或者误以为 go 一下就万事大吉。这些细节不抠,跑几天就 OOM 或慢得像单线程。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










