ssd上并发数设4–16个worker即可,机械硬盘限2–4个;盲目高并发会因寻道竞争和上下文切换降低吞吐,应通过信号量控制并发、单goroutine写入同一文件,并结合iostat监控%util与await判断磁盘瓶颈。

并发数设多少才不拖慢磁盘 I/O
SSD 上通常 4–16 个并发 worker 就够用,机械硬盘建议压到 2–4 个。盲目开几百个 goroutine 不会变快,反而因寻道竞争和上下文切换拉低吞吐。
- 用
sem := make(chan struct{}, 10)做信号量比无限制启 goroutine 更稳 - 每个 worker 处理一个完整文件(而非分片),避免同一文件多 goroutine 争抢
*os.File - 若写入目标是同一文件,必须串行——用单个 goroutine 消费
resultChan再写,别让多个 goroutine 直接file.Write - 观察
iostat -x 1中的%util和await:如果 %util 接近 100% 但 await 很高,说明磁盘已成瓶颈,再加并发只会恶化
大文件别用 os.ReadFile 一锅端
os.ReadFile 把整个文件读进内存,几 GB 文件直接触发 GC 或 OOM。真要处理大文件,得流式分块读。
- 用
os.Open+bufio.NewReaderSize(file, 4*1024*1024)控制缓冲区为 4MB - 读取时检查
n, err := reader.Read(buf)返回值:err == io.EOF是正常结束,n == 0 && err == nil才是空读 - 对日志类按行处理的文件,优先用
scanner := bufio.NewScanner(file),它内部自动缓冲且不会截断单行 - 别在循环里反复
make([]byte, size),用sync.Pool复用 buffer,比如var bufPool = sync.Pool{New: func() any { return make([]byte, 1
结果收集别卡死在 channel 上
主 goroutine 从 resultChan 收集时,如果没控制好缓冲或关闭时机,容易死锁或内存堆积。
- channel 必须带缓冲:
results := make(chan Result, len(filePaths)),容量至少等于任务数 - worker 全部启动后,立刻
close(jobs);主 goroutine 在wg.Wait()后再close(results) - 收结果别用
for res := range results等到 channel 关闭——万一某个 worker panic 没发结果,就会永远阻塞。改用for i := 0; i - 如果某 worker 处理失败,仍要发
Result{Error: err}到 channel,否则主 goroutine 少收一个就卡住
写入阶段最容易被忽略的三个细节
读得再快,写崩了整条流水线就废。写操作不是“发完就完”,尤其涉及落盘一致性时。
- 别裸写
file.Write,包一层bufio.NewWriterSize(file, 1,批量写+显式 <code>Flush() - 需要数据不丢?在
Flush()后补一句file.Sync(),但别每写一次都 sync——攒够一批再调,否则 I/O 毛刺明显 - 多个输出文件时,每个 worker 独占一个
*os.File句柄,别共用;句柄用完必须Close(),否则 Linux 下很快 hit ulimit
pprof 和 iostat 对着看,比调并发数重要得多。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











