WaitGroup 负责等待所有 goroutine 结束,channel 负责传递结果或协调节奏;正确做法是启动前 wg.Add(n),每个 goroutine 结尾 defer wg.Done() 后再发结果到 channel,并在所有发送完成后关闭 channel。

WaitGroup 和 channel 各自该管什么
WaitGroup 负责“等所有 goroutine 结束”,channel 负责“传结果或协调节奏”。混用时最常见错误是:用 channel 去替代 WaitGroup 的等待逻辑,比如反复 range 一个未关闭的 channel 导致死锁,或者漏掉 wg.Done() 还指望从 channel 收完就自动退出。
正确分工:wg.Add() 在启动 goroutine 前调用;每个 goroutine 结尾必须调用 wg.Done()(最好用 defer);channel 仅用于输出结果、限流或通知完成,不承担生命周期管理职责。
并发任务结果收集的典型写法
批量任务通常需要汇总返回值,这时 channel 是自然选择,但必须配合 WaitGroup 确保所有发送完成后再关闭 channel。否则消费者可能提前退出,漏收数据。
- 启动前
wg.Add(n),n 是任务总数 - 每个 goroutine 执行完后
defer wg.Done(),再向 channel 发送结果(如ch ) - 另起一个 goroutine 调用
wg.Wait(),然后close(ch) - 主 goroutine 用
for range ch安全接收,无需担心 channel 未关闭
ch := make(chan int, 10)
wg := &sync.WaitGroup{}
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
ch <- id * 2
}(i)
}
go func() {
wg.Wait()
close(ch)
}()
for res := range ch {
fmt.Println(res)
}为什么不能只用 channel 不用 WaitGroup
只靠 close(ch) 并不能保证所有 goroutine 已执行完毕——你可能在某个 goroutine 还没走到 ch 那行时就关闭了 channel,导致它 panic:send on closed channel。反过来,如果不用 channel 而只用 wg.Wait(),又拿不到中间结果。
小邮包-包月订购包年服务网,该程序由好买卖商城开发,程序采用PHP+MYSQL架设,程序商业模式为目前最为火爆的包月订制包年服务模式,这种包年订购在国外网站已经热火很多年了,并且已经发展到一定规模,像英国的男士用品网站BlackSocks,一年的袜子购买量更是达到了1000万双。功能:1、实现多产品上线,2、不用注册也可以直接下单购买,3、集成目前主流支付接口,4、下单发货均有邮件提醒。
关键点在于:WaitGroup 保证“执行结束”,channel 保证“通信安全”。二者不是替代关系,而是互补关系。尤其当任务耗时差异大、部分失败需继续执行其余任务时,这种组合能避免过早关闭 channel 或遗漏结果。
容易被忽略的缓冲区和关闭时机
channel 缓冲区大小设太小会导致 goroutine 阻塞在发送端,拖慢整体进度;设太大则浪费内存。更隐蔽的问题是关闭 channel 的 goroutine 可能比所有 worker 启动得还慢,造成竞态。
- 缓冲区建议设为任务数
cap(ch) == n,或略大(如n+1),避免阻塞 -
close(ch)必须在wg.Wait()之后,且只能由一个 goroutine 执行 - 不要在 worker 内部判断 “我是最后一个” 然后关 channel —— 没有可靠方式识别“最后一个”
- 如果任务可能 panic,
defer wg.Done()仍有效,但需额外 recover,否则 channel 不会关闭
实际项目里,真正难的不是写对这几行,而是想清楚:哪些数据必须按序收?哪些可以丢?失败要不要重试?这些决策会直接决定 channel 类型(带缓冲/无缓冲)、是否用带超时的 select、以及是否引入 error channel。









