根本原因是高并发下争抢文件描述符、磁盘I/O队列和内核缓冲区;建议用带缓冲channel限制并发数,如sem := make(chan struct{}, 10)。

goroutine 并发读文件时为什么反而变慢?
直接用 go readFile(filename) 启动几十个 goroutine 读大文件,常出现整体耗时比串行还长。根本原因不是 goroutine 本身慢,而是底层 os.Open 和 io.Read 在高并发下争抢系统文件描述符、磁盘 I/O 队列和内核缓冲区,尤其在机械硬盘或 NFS 上更明显。
实操建议:
- 限制并发数:用带缓冲的 channel 控制活跃 goroutine 数量,例如
sem := make(chan struct{}, 10),每次读前sem ,读完后 - 避免重复打开同一文件:若多个 goroutine 需读同一文件,先
os.Open一次,传入*os.File,而非反复调用os.ReadFile - 对小文件(os.ReadFile + goroutine 即可;大文件务必用
bufio.NewReader分块读,减少系统调用次数
如何安全地并发写同一个文件?
多个 goroutine 直接 f.Write() 到同一个 *os.File 会引发数据错乱——Go 的 Write 不是原子操作,底层 write(2) 调用可能被调度打断。即使加了 mutex,也无法保证写入顺序和偏移位置正确。
正确做法分场景:
立即学习“go语言免费学习笔记(深入)”;
- 追加写(append):用
os.O_APPEND标志打开文件,此时内核保证每次write(2)原子性追加,配合sync.Mutex保护写内容构造逻辑即可 - 随机写 / 覆盖写:必须由单个 goroutine 统一处理写请求,其他 goroutine 通过 channel 发送
struct{ offset int64; data []byte }到 writer goroutine - 写不同文件:最简单,每个 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/Writer 在 goroutine 中要注意什么?
bufio.Reader 和 bufio.Writer **不是并发安全的**。它们内部缓存数据并维护读写位置,多个 goroutine 同时调用 ReadString 或 WriteString 会导致 panic 或数据污染。
使用前提:
- 每个 goroutine 必须持有自己独立的
bufio.Reader或bufio.Writer实例 - 不能把同一个
bufio.Reader传给多个 goroutine,哪怕只读也不行(因为Read会移动内部r.buf和r.r) - 对网络连接(如
net.Conn)做并发读写时,应拆成两个 goroutine:一个专读(配独立bufio.Reader),一个专写(配独立bufio.Writer)
为什么 defer file.Close() 在 goroutine 里容易漏掉?
典型错误:
go func(name string) {
f, _ := os.Open(name)
defer f.Close() // 这行永远不会执行!
// ... 处理逻辑
}("data.txt")因为 goroutine 启动后立即返回,外层函数结束,但该 goroutine 可能还没运行到 defer 行就因 panic 或提前 return 退出,defer 不触发。
可靠写法:
- 把
Close()放在业务逻辑末尾,显式调用 - 用
errgroup.Group管理 goroutine 生命周期,确保所有 goroutine 结束后统一 Close - 对临时文件,用
os.CreateTemp+defer os.Remove配合显式Close
真正难处理的是「写一半出错」的情况——必须检查 Writer.Flush() 和 Close() 的返回值,否则缓存中的数据可能静默丢失。










