并发调用 os.File.Write 会因文件偏移量竞争导致数据错乱;用 chan []byte 串行化写入可避免竞态,建议缓冲区设为128~1024,并确保关闭channel和等待goroutine结束。

为什么直接用 os.File.Write 并发写同一个文件会出问题
多个 goroutine 同时调用 os.File.Write 写入同一个 *os.File,会导致数据错乱或覆盖——因为 Write 操作不是原子的,底层依赖文件偏移量(offset),而并发修改偏移量没有同步机制。即使加了 sync.Mutex,也容易因阻塞影响吞吐,且无法天然支持任务排队、限流、取消等需求。
用 chan []byte 实现串行化写入最简方案
核心思路:所有写请求统一发到一个 channel,由单个 goroutine 顺序消费并调用 file.Write。这样既避免竞争,又保持逻辑清晰。
实操建议:
- 定义 channel 类型为
chan []byte,而非chan string,避免每次写入都额外分配字符串头(尤其高频小写入时) - channel 缓冲区大小建议设为
128~1024,太小易阻塞生产者,太大则内存浪费且延迟不可控 - 务必在程序退出前关闭 channel 并等待 writer goroutine 结束,否则可能丢失最后几条日志
context.Context
- 写入请求走
chan writeReq,其中writeReq包含data []byte、done chan - 主 writer goroutine 在
select中监听ctx.Done(),及时退出 - 每个写入操作后,必须把
error发回对应donechannel,否则调用方永远阻塞 - 注意:不要在 writer 中直接
log.Fatal或 panic,应交由上层处理错误
典型错误现象:fatal error: all goroutines are asleep - deadlock,基本是因为某次写入后忘了发 error 到 done,或调用方没接收就退出。
什么时候该换用 sync.Pool + bufio.Writer
当单次写入量小(如每条日志 1k QPS),纯 channel 转发 + 直接 file.Write 会产生大量系统调用开销。
这时应叠加缓冲:
- 用
bufio.NewWriterSize(file, 64*1024)包一层,减少 write 系统调用次数 - 配合
sync.Pool复用bytes.Buffer或预分配切片,避免高频分配 - 注意
Flush时机:不能只靠 buffer 满了才刷,需额外起 goroutine 定期flushTicker,否则日志延迟不可控 - 别在 writer goroutine 里直接调用
bufio.Writer.Flush后立刻Close—— 关闭前必须确保最后一次 flush 已完成,否则末尾数据丢失
真正容易被忽略的点:buffer 尺寸和 flush 频率之间是权衡关系。设太大,延迟高;设太小或 flush 太勤,又抵消了缓冲收益。上线前得用真实日志流量压测确认。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











