sync.mutex仅限单进程内有效,无法解决多进程写冲突;跨进程必须用flock等文件锁,且需所有进程遵守锁协议。

sync.Mutex 不能解决多进程写冲突
它只在单个 Go 进程内有效,对其他进程完全无效。两个独立的 Go 程序(比如一个 cron job 和一个 web server)同时打开同一文件并写入,sync.Mutex 像没存在一样——文件照样被截断、覆盖、出现空字节。
常见错误现象包括:os.O_TRUNC 被多个进程并发触发,后者清空文件后前者再写,内容全丢;或读取中被 file.Truncate(0) 中断,返回乱码或 EOF。
如果你的场景是“多个 goroutine 写同一文件”,sync.Mutex 可用;但只要涉及“多个进程”,就必须换方案。
flock 是跨进程文件锁的事实标准
Linux/macOS 下直接用 golang.org/x/sys/unix.Flock,Windows 下必须用 github.com/go-flock/flock 或 windows.LockFileEx —— 别试图自己封装 syscall,平台差异太大。
关键约束有三点:
- 文件必须以
os.O_RDWR打开(os.O_WRONLY也行),os.O_RDONLY会返回syscall.EBADF - 锁绑定的是文件描述符(fd),不是路径;同一进程多次
os.OpenFile同一路径,得到不同 fd,需各自加锁 -
unix.LOCK_EX | unix.LOCK_NB是推荐组合:非阻塞尝试,失败时可重试或快速返回,避免 goroutine 卡死
示例片段:
f, err := os.OpenFile("log.txt", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
if err != nil {
return err
}
defer f.Close() // 自动释放锁,但别依赖它做逻辑顺序控制
if err := unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil {
if errors.Is(err, unix.EAGAIN) {
return fmt.Errorf("lock busy")
}
return err
}
// ✅ 此时可安全写入
_, _ = f.Write([]byte("hello\n"))
不要误信 os.O_APPEND 能替代 flock
即使所有写入都带 os.O_APPEND,在 NFS、某些容器挂载卷或高并发下,仍可能因内核调度导致写入重叠 —— O_APPEND 保证每次 write 前 seek 到末尾,但不保证“seek + write”原子性。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
实测中常见问题:
- 两行日志写到同一 offset,后写者覆盖前写者结尾
- 一行日志被切成两段,中间插入另一行开头
- 文件末尾出现零字节块或长度异常(
go run -race会报Write at 0x... by goroutine N)
所以 O_APPEND 是辅助手段,不是锁的替代品。
高频写入场景优先用 chan + 单 writer goroutine
当写请求密集(如 HTTP 请求日志),用 flock 每次都加锁会成瓶颈;而 sync.Mutex 在多进程下又无效。此时应放弃“保护共享 fd”,转向“消除共享”。
推荐做法:
- 用
chan []byte收集写请求,由单一 goroutine 消费并批量落盘 - 搭配
bufio.Writer和可控Flush()频率,减少系统调用次数 - 每个写入方只发数据,不碰文件句柄,天然无锁、无竞争
注意:该方案仍需配合 flock 或临时文件 rename,以防多个进程启动各自的 writer goroutine —— 它解决的是进程内 goroutine 竞争,不解决跨进程竞争。
真正容易被忽略的点是:锁协议必须被所有参与者遵守。flock 是 advisory lock,不拦截未加锁的写操作。只要有一个进程绕过锁直接写,整个机制就失效。所以锁不是加了就完事,而是要统一部署、统一管控、统一验证。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










