直接用for分片比chan分发更轻量,因避免调度开销和关闭竞态;需按索引切分、校验边界、合理设并发数与分片大小,并用sync.WaitGroup归并结果。

直接用 for 分片比 chan 分发更轻量
当原始数据是已加载到内存的 []int64 或 []string(比如从 DB 一次性查出的百万级用户 ID),直接按索引切分最稳。用 chan 做任务分发反而引入调度开销和关闭竞态——实测 Go 1.21–1.23 下,小数据量时性能低 8–12%。
- 切片索引分片公式:
start := i * chunkSize,end := min(start+chunkSize, len(ids)),必须做end <= len(ids)检查,否则 panic - 别在循环里直接捕获
i:for i := range ids { go process(ids[i]) }会全部处理最后一个元素;应传参:go process(ids[start:end]) - 并发数别硬写 100:用
runtime.NumCPU()作上限参考,避免 goroutine 过多导致调度器过载
sync.WaitGroup 比 chan 更适合结果归并
不需要流式消费中间结果时,sync.WaitGroup 是更干净的选择。它不拷贝数据、不缓冲、不关 channel,生命周期可控,也避开 send on closed channel 这类典型 panic。
- 务必在启动 goroutine 前调用
wg.Add(1),并在 goroutine 内部defer wg.Done();漏掉任一环节都会卡死 - 每个分片返回独立结构体(如
struct{ Success int; Failed []int64 }),主 goroutine 最后统一 merge;别让所有 goroutine 往同一个[]errorappend - 若需收集失败 ID,预分配目标 slice 容量:
errs := make([]int64, 0, len(ids)/10),避免多次 realloc
分片大小不是越大越好,4MB 或 10k ID 是较优起点
分片太小 → goroutine 创建/销毁开销占比上升;太大 → 单个任务耗时拉长,拖慢整体完成时间,且容易触发 GC 压力。对用户 ID 这类轻量数据,按条数切比按字节切更直观。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 推荐起始值:
chunkSize := len(ids) / runtime.NumCPU() + 1,确保最后一片不为空 - 如果单个 ID 处理含 HTTP 调用或 DB 写入,分片建议 ≤ 1000 条;纯内存计算可放宽至 10k
- 避免固定写死
chunkSize := 5000—— 在 4 核机器上分 100 片,在 32 核上还是 100 片,显然不合理
别用 sync.Pool 缓存用户 ID 切片本身
sync.Pool 适合复用大块字节缓冲区(如 []byte),但不适合缓存业务逻辑中的 []int64 或 []string。它解决的是内存分配频次问题,不是 CPU 并行瓶颈。
立即学习“go语言免费学习笔记(深入)”;
- 强行池化 ID 切片会引入锁竞争,掩盖真实瓶颈:你真正要并行的是处理逻辑,不是“持有 ID”这个动作
- pprof 显示
runtime.mallocgc占比高但 CPU 利用率不足 40%,说明你在优化错的方向 - 真正该复用的是下游调用的 client 实例(如
*http.Client)、加密哈希对象(sha256.Hash)或 JSON 解析器(json.Decoder)
分片边界是否对齐、并发数是否匹配硬件、结果归并是否引入锁争用——这三个点,比选什么框架或库更容易决定最终性能。


















