最常被选中的折中方案是用 chan struct{ index int; value t } 收集结果再排序:保留 channel 的流式能力,又能让结果归位,适合任务动态生成或不预分配大 slice 的场景,每个 goroutine 发送前须带原始索引。

用 chan struct{ Index int; Value T } 收集结果再排序
这是最常被选中的折中方案:保留 channel 的流式能力,又能让结果归位。适合任务动态生成、或你不想预分配大 slice 的场景。
- 每个 goroutine 发送前必须带上原始索引,比如
ch - 主协程收满
len(tasks)个后,用sort.Slice按Index排序,再提取Value - 别用
map[int]T存中间结果——忘了加锁会 panic;用make([]T, n)配合索引写入更稳 - 排序是 O(n log n),但对几千以内任务影响不大;若任务数超 10 万,直接上 WaitGroup + 索引数组更快
sync.WaitGroup + 预分配 []T 是最稳写法
如果你的任务列表长度确定、结果类型明确,这是零风险选择。它不依赖 channel 调度,也不引入额外排序开销。
- 提前声明
results := make([]Result, len(tasks)),所有 goroutine 共享这个切片 - 每个 goroutine 接收自己的
i(注意传值闭包!别用循环变量),处理完直接写results[i] = res - 主协程调用
wg.Wait()后,results就已是严格按原始顺序排列的 - 切片必须在 goroutine 外声明并传入;若用
append动态增长,可能触发底层数组复制,导致写入错位
别用 select 从多个 case 读同一个 chan T
这种写法看似“并发接收”,实则完全不可控。Go 的 select 对就绪 channel 是随机挑选的,和发送顺序无关。
- 即使你按 0→1→2→3 启动 goroutine 并发写入同一 channel,
select读出来的顺序可能是 2、0、3、1 - 无缓冲 channel 不解决这个问题;缓冲大小只影响阻塞行为,不改变
select的非确定性 - 真正需要顺序时,channel 只能当“运输管道”,不能当“排序器”——序号必须由业务逻辑显式携带
每个 goroutine 分配独立 chan T 也能保序
这是最容易被忽略的干净解法:用切片存通道,主协程按索引顺序读,天然规避竞争和调度干扰。
- 声明
chs := make([]chan Result, len(tasks)),然后chs[i] = make(chan Result, 1) - 每个 goroutine 只往
chs[i]写一次,且只写一个值 - 主协程用
for i := range tasks { res := ,顺序完全由 for 循环控制 - 内存开销略高(每个 channel 约 300 字节),但逻辑清晰、无锁、无排序、无竞态,调试也直观
实际项目里,索引数组方案(WaitGroup + []T)覆盖了八成以上批量有序收集需求。剩下两成,要么是任务流式产生,要么是结果要边收边处理——这时才轮到带索引的 channel 或独立 channel 出场。别为了“看起来高级”而绕远路,顺序这件事,越直白越可靠。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











