根本原因是高并发下争抢文件描述符、磁盘I/O队列和内核缓冲区;建议用带缓冲channel限制并发数,如sem := make(chan struct{}, 10)。
直接用 go readFile(filename) 启动几十个 goroutine 读大文件,常出现整体耗时比串行还长。根本原因不是 goroutine 本身慢,而是底层 os.Open 和 io.Read 在高并发下争抢系统文件描述符、磁盘 I/O 队列和内核缓冲区,尤其在机械硬盘或 NFS 上更明显。
实操建议:
sem := make(chan struct{}, 10),每次读前 sem ,读完后
os.Open 一次,传入 *os.File,而非反复调用 os.ReadFile
bufio.NewReader 分块读,减少系统调用次数多个 goroutine 直接 f.Write() 到同一个 *os.File 会引发数据错乱——Go 的 Write 不是原子操作,底层 write(2) 调用可能被调度打断。即使加了 mutex,也无法保证写入顺序和偏移位置正确。
正确做法分场景:
os.O_APPEND 标志打开文件,此时内核保证每次 write(2) 原子性追加,配合 sync.Mutex 保护写内容构造逻辑即可struct{ offset int64; data []byte } 到 writer goroutine*os.File,无需同步file, _ := os.OpenFile("out.bin", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
defer file.Close()
var mu sync.Mutex
go func() {
mu.Lock()
file.Write([]byte("log line\n"))
mu.Unlock()
}()bufio.Reader 和 bufio.Writer **不是并发安全的**。它们内部缓存数据并维护读写位置,多个 goroutine 同时调用 ReadString 或 WriteString 会导致 panic 或数据污染。
使用前提:
bufio.Reader 或 bufio.Writer 实例bufio.Reader 传给多个 goroutine,哪怕只读也不行(因为 Read 会移动内部 r.buf 和 r.r)net.Conn)做并发读写时,应拆成两个 goroutine:一个专读(配独立 bufio.Reader),一个专写(配独立 bufio.Writer)典型错误:
go func(name string) {
f, _ := os.Open(name)
defer f.Close() // 这行永远不会执行!
// ... 处理逻辑
}("data.txt")因为 goroutine 启动后立即返回,外层函数结束,但该 goroutine 可能还没运行到 defer 行就因 panic 或提前 return 退出,defer 不触发。
可靠写法:
Close() 放在业务逻辑末尾,显式调用errgroup.Group 管理 goroutine 生命周期,确保所有 goroutine 结束后统一 Closeos.CreateTemp + defer os.Remove 配合显式 Close
真正难处理的是「写一半出错」的情况——必须检查 Writer.Flush() 和 Close() 的返回值,否则缓存中的数据可能静默
