sync.waitgroup用于安全等待一组goroutine结束,需确保add在go前调用、done用defer保证执行、避免闭包变量复用、禁止值传递waitgroup,并配合context.context和协程池实现高并发稳定性。

直接上结论:高并发不是靠堆 go 关键字堆出来的,而是靠「边界控制 + 协同等待 + 请求生命周期管理」三层结构稳住的。没这三块,代码写得再快,压测一跑就崩。
怎么用 sync.WaitGroup 安全等一组 Goroutine 结束
很多人在循环里启动 Goroutine 后直接 wg.Wait(),结果 panic 或漏等——根本原因是 Add 调用时机错、闭包变量复用、或 Done 没覆盖所有退出路径。
-
Add必须在go语句之前调用,且传入值不能为负;常见错误是放到 Goroutine 内部去加 - 循环变量直接在闭包中引用(如
for _, v := range list { go func() { use(v) }() })会导致所有 Goroutine 读到同一个最终值;必须显式传参:go func(val T) { use(val) }(v) - 如果 Goroutine 内可能 panic,
defer wg.Done()是唯一安全写法;别写成wg.Done()放在最后,panic 会跳过它 - 不要在
WaitGroup上做多次Wait,它不是可重入的;需重复使用时应重建实例
为什么 context.Context 不能只传给顶层函数
context 不是“带个超时参数”就完事了。它要贯穿整个请求链路,否则下游 I/O(如数据库查询、HTTP 调用、文件读写)根本收不到取消信号。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 每个可能阻塞的操作都该接受
ctx参数,比如db.QueryContext(ctx, sql)、http.DefaultClient.Do(req.WithContext(ctx)) - 自定义子 Context(如
WithTimeout)必须在进入新逻辑前创建,不能在 Goroutine 里现场建——那样会丢失父子取消关系 - 别把
context.Background()当万能兜底;长期运行的后台任务用它没问题,但 HTTP handler 必须用req.Context() -
ctx.Err()返回非 nil 后,ctx.Value()仍可读,但不应再发起新 I/O;很多中间件漏判这个状态,继续发请求导致资源浪费
并发任务失控时,第一反应不该是加机器,而是加池
看到 QPS 上不去、CPU 飙高、OOM 频发,90% 是因为无限制启 go processRecord(r)。Goroutine 池不是性能优化项,是稳定性底线。
- 池大小不是拍脑袋定的;建议从
runtime.NumCPU()开始试,再结合下游资源瓶颈(如 DB 连接池大小、RPC 限流阈值)向下收敛 - 标准库没有现成池,别自己手撸;优先用
golang.org/x/sync/errgroup.Group(自带WithContext)或成熟方案如panjf2000/ants - 池提交任务后必须检查返回错误;被拒绝的任务(如池满)不能静默丢弃,得降级走同步执行或写入消息队列保底
- 池本身不解决数据竞争;若多个 worker 共享 map 或 slice,还得配
sync.RWMutex或改用线程安全结构
最常被跳过的一步:没给每个 Goroutine 设置栈上限和 panic 恢复。一个没 recover 的 panic 会让整个 goroutine 死掉,而 WaitGroup 还在等它——结果是永远卡在 Wait,服务假死。这比功能 bug 更难排查。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










