明确知道任务数量时,应使用带缓冲的 channel(容量为 n)收集结果;主协程显式接收 n 次,避免 close 依赖与死锁;用结构体统一传递结果与错误;需顺序输出则为每个 goroutine 分配独立 channel;涉及错误、超时或取消时必须用 channel 而非 waitgroup。

用带缓冲 channel 收集确定数量的结果
当明确知道要启动 N 个 goroutine 并期望收到 N 个结果时,make(chan T, N) 是最直接安全的选择。缓冲区容量设为任务总数,能避免 sender 阻塞,也无需在每个 goroutine 里 close() ——主协程只需按需接收 N 次即可。
- 不依赖
close(),避免因某个 goroutine panic 导致 channel 未关闭而引发死锁 - 主协程用
for i := 0; i 显式控制接收次数,逻辑清晰、不易出错 - 若 goroutine 可能 panic 或提前退出,记得在 sender 侧用
defer recover()防泄漏,但更推荐用context统一控制生命周期
需要保持启动顺序时,别共用一个 channel
所有 goroutine 往同一个 channel 写结果,再顺序读取,得到的一定是完成顺序,不是启动顺序。这是并发调度的天然特性,不是 bug。
- 正确做法是为每个 goroutine 分配独立的
chan Result,比如chans[i] = make(chan Result, 1) - 主协程按索引
i依次执行result := ,天然保证输出与启动顺序一致 - 不用
select或range,避免引入非确定性;每个通道只写一次、读一次,简单可靠
错误和结果混传,优先用结构体 + 单一 channel
不要为每个 goroutine 开两个 channel(一个 data,一个 error),容易漏收、难管理,还可能造成 goroutine 泄漏。
- 定义统一的
Result结构体,包含Val和Err字段,强制每个任务必须返回完整状态 - 所有 goroutine 共享一个
chan Result,无论成功失败都发一次,主协程统一检查result.Err != nil - 如果某任务失败后想中止其余任务,配合
context.WithCancel()向所有 goroutine 传播信号,而不是靠关闭 channel 或轮询标志位
WaitGroup + 共享切片只适合极简场景
当任务无返回值、或结果可预先分配且写入无竞争(如写入不同 slice 索引),sync.WaitGroup 确实更轻量。但它本身不传递数据,也不处理错误。
- 必须确保每个 goroutine 写入的是互不重叠的内存位置,否则需加
sync.Mutex或改用原子操作 - 无法感知单个任务失败,只能等全部结束;若中间出错,主协程无法及时响应
- 一旦用了共享变量,就失去了 channel 天然的同步语义,调试和扩展成本明显上升
真正难的不是选 channel 还是 WaitGroup,而是判断「结果是否需要参与控制流」——只要涉及错误分支、超时中断、取消传播,就必须让结果带着上下文走 channel,而不是藏在闭包变量里。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











