
本文揭示一个典型 Go 并发陷阱:缓冲区变大后 goroutine 无输出,并非因 channel 行为异常,而是因 sync.WaitGroup.Add() 调用时机错误导致的竞态——必须在 goroutine 启动前调用 Add,否则 Wait() 可能提前返回,造成主协程过早关闭通道并退出。
本文揭示一个典型 go 并发陷阱:缓冲区变大后 goroutine 无输出,并非因 channel 行为异常,而是因 `sync.waitgroup.add()` 调用时机错误导致的竞态——必须在 goroutine 启动前调用 `add`,否则 `wait()` 可能提前返回,造成主协程过早关闭通道并退出。
在 Go 并发编程中,sync.WaitGroup 是协调 goroutine 生命周期的关键工具,但其正确使用高度依赖调用顺序。原代码看似逻辑清晰:启动 7 个读取 goroutine、向带缓冲 channel 发送 100 个整数、关闭输入通道、等待所有读取完成、再关闭输出通道。然而,问题出在 wg1.Add(1) 的位置:
func readInt(in chan int, out chan string, id int) {
wg1.Add(1) // ❌ 错误:在 goroutine 内部调用 Add
for n := range in {
out <- fmt.Sprintf("goroutine:%d was sent %d", id, n)
}
wg1.Done()
}该写法违反了 WaitGroup 的核心契约:Add() 必须在 Wait() 被调用前执行,且必须在 goroutine 实际开始工作前完成计数。由于 go readInt(...) 是异步启动,主 goroutine 在循环发送数据、close(share) 和 wg1.Wait() 之间几乎不阻塞;而 readInt 内部的 wg1.Add(1) 可能尚未执行,wg1.Wait() 就已返回(此时计数仍为 0),导致 close(share) 后主协程立即执行 close(out) 并退出——所有 readInt 和 printChan goroutine 还未真正开始消费,甚至可能被系统调度器延迟执行。
有趣的是,小缓冲区(如 make(chan int, 10))“偶然”掩盖了该问题:因为发送操作 share
✅ 正确做法是:在 go 语句之前调用 Add(),确保计数与 goroutine 启动严格同步:
func main() {
share := make(chan int, 100) // 缓冲区大小 now irrelevant
out := make(chan string)
go printChan(out)
// ✅ 正确:Add 在 goroutine 启动前调用
wg1.Add(7)
for j := 1; j <= 7; j++ {
go readInt(share, out, j)
}
for i := 1; i <= 100; i++ {
share <- i
}
close(share)
wg1.Wait() // 现在能正确等待全部 7 个 goroutine 完成
close(out)
wg2.Wait()
}
func readInt(in chan int, out chan string, id int) {
// ✅ 移除内部 Add;Done 保持不变
for n := range in {
out <- fmt.Sprintf("goroutine:%d was sent %d", id, n)
}
wg1.Done()
}
func printChan(out chan string) {
wg2.Add(1) // 此处 Add 位置正确(单个 goroutine,且在 go 前隐含)
for l := range out {
fmt.Println(l)
}
wg2.Done()
}关键注意事项:
- WaitGroup 的 Add()、Done()、Wait() 三者必须遵循“先 Add,再 Wait,最后 Done”的时序约束;
- Add() 绝不可放在 goroutine 内部(除非配合额外同步机制,如 sync.Once,但本场景无必要);
- 缓冲区大小影响的是 channel 的阻塞行为,而非 WaitGroup 的语义——它只是暴露了并发逻辑缺陷的“放大器”;
- 生产代码中建议使用 defer wg.Done() 确保 Done() 总被执行,并考虑使用结构化并发(如 errgroup.Group)替代裸 WaitGroup。
通过修正 Add() 调用位置,无论缓冲区设为 1、10、100 或 0(无缓冲),程序均能稳定输出全部 700 行日志(100 输入 × 7 goroutines),真正实现预期的并发处理语义。


















