应预分配切片并用索引赋值,或各goroutine写独立子切片后合并,或用带缓冲channel配合close避免死锁;直接并发append会导致内存竞态panic。

用 sync.WaitGroup + 共享切片收集结果,但要注意并发写入 panic
直接往一个 []int 里 append 多个 goroutine 的结果,大概率触发 fatal error: concurrent map writes 或 slice 扩容时的竞态——Go 的 slice 底层是结构体(指针+长度+容量),append 可能重分配底层数组,多个 goroutine 同时操作会破坏内存一致性。
- 正确做法:预分配好切片,用索引赋值,避免
append - 或者用
sync.Mutex包裹append操作(小数据量可接受,但锁竞争会影响吞吐) - 更推荐:每个 goroutine 写入独立子切片,最后由主 goroutine 合并(无锁、易调试)
channel 是最自然的汇总方式,但别忘了关闭和缓冲
用 chan int 接收各协程结果,主 goroutine range 读取,是最符合 Go 并发模型的做法。但常见错误是忘记关闭 channel,导致 range 永远阻塞;或用无缓冲 channel 且发送方未被等待,造成死锁。
- 发送方必须在写完后
close(ch),且只能由一个 goroutine 关闭(通常放在sync.WaitGroup的 Done 后) - 若结果量大或不确定,建议用带缓冲的 channel:
ch := make(chan int, 100),避免发送方因接收方慢而卡住 - 接收方用
for v := range ch,不要用for { select { case v := 配合 break,容易漏值或提前退出
需要按顺序汇总?别依赖 goroutine 启动顺序
goroutine 启动快不代表执行完得早,go f(0); go f(1); go f(2) 不保证结果按 0/1/2 顺序到达。如果业务要求结果与输入顺序严格对应(比如批量请求后保持响应顺序),不能靠 channel 收到的先后顺序。
- 方案一:每个 goroutine 返回
struct{ Index int; Value int },主 goroutine 收集后按Index排序 - 方案二:用带索引的 channel 发送,例如
ch - 方案三:预分配结果切片,传入 index 和指向该位置的指针(或直接用
results[i] = compute(i),配合WaitGroup等待全部完成)
大量结果时,sync.Map 或 map + sync.RWMutex 不是首选
有人想用 sync.Map 存 key→value 映射来汇总,比如把任务 ID 当 key。这在键不重复且需随机访问时可行,但对纯顺序汇总场景是过度设计——sync.Map 开销比普通 map 大,且不支持遍历顺序保证;map + 互斥锁又引入锁竞争。
- 除非你明确需要按 key 查找、去重、或后续高频查询,否则别用 map 做结果容器
- 简单汇总优先选 slice(预分配)或 channel(流式处理)
- 如果结果要聚合统计(如 sum/count/min/max),可让每个 goroutine 返回局部统计值,主 goroutine 归并,减少共享变量压力
实际最难的不是怎么收,是怎么界定“所有协程已完成”——WaitGroup 计数漏减、channel 过早关闭、recover 吞掉 panic 导致 goroutine 静默退出,都会让汇总逻辑永远等不到全部结果。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











