sync.mutex对文件并发读写无效,因其仅作用于进程内内存变量;必须使用flock(linux/macos)或windows独占句柄等内核级文件锁,且需注意锁粒度、生命周期及跨平台兼容性。

Go 语言里对同一文件做并发读写,sync.Mutex 或 sync.RWMutex 完全无效——它们只锁内存变量,不跨进程、不作用于磁盘文件。真要防止多进程/多实例同时改一个文件,必须用文件系统级锁,也就是 flock(Linux/macOS)或等效 Windows 机制。
为什么 sync.Mutex 对文件操作没用
很多人误以为在写文件前加个 mu.Lock() 就能保护文件,其实它只阻止了当前进程内多个 goroutine 同时执行那段代码,但完全拦不住另一个独立进程(比如重启的程序、crontab 脚本、K8s 里另一个 Pod)打开并写入同一文件。
-
sync.Mutex是用户态内存锁,生命周期绑定 Go 程序运行时,进程退出即消失 - 文件锁是内核级资源,由 inode 和文件描述符关联,即使进程崩溃,只要 fd 关闭,锁自动释放
- 典型错误:用
os.OpenFile+mu.Lock()写配置,结果两个实例同时写,配置被截断或覆盖
flock 在 Linux/macOS 上怎么用
标准库没封装,得靠 syscall.Flock,注意它只接受已打开的 *os.File 的文件描述符,且必须用 O_RDWR 或 O_WRONLY 打开(只读文件无法上写锁)。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
- 加独占锁(写锁):
syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB),LOCK_NB表示非阻塞,失败立刻返回错误,避免 goroutine 卡住 - 加共享锁(读锁):
syscall.Flock(int(f.Fd()), syscall.LOCK_SH|syscall.LOCK_NB),多个进程可共存,但会阻塞后续写锁 - 解锁统一用:
syscall.Flock(int(f.Fd()), syscall.LOCK_UN) - 务必在
defer f.Close()前解锁,否则f.Close()会自动释放锁,但顺序错乱可能引发竞态
Windows 下怎么实现等效文件锁
Windows 没 flock,但可用 syscall.CreateFile 配合 FILE_SHARE_NONE 实现类似语义:以独占方式打开文件句柄,其他进程再以任何方式打开该路径都会失败。
- 不能复用 Unix 代码,
syscall.Flock在 Windows 上未定义,编译直接报错 - 推荐用
github.com/go-flock/flock这类第三方库,它内部做了构建标签分发(//go:build windows///go:build !windows),对外提供统一TryLock()接口 - 手动实现时注意:Windows 文件锁基于路径,不是 inode;若文件被重命名或硬链接,锁行为与 Unix 不一致
- 避免用
os.Chmod或os.Rename操作被锁文件,可能触发权限异常或锁丢失
常见踩坑点:锁粒度、协议与清理
文件锁本身简单,但落地时最容易栽在“谁来管”和“什么时候放”上。
- 别在每次写文件前临时
os.Open加锁——锁的生命周期应覆盖整个业务逻辑块,比如“读→校验→修改→写入→fsync”,而不是只锁f.Write()那一行 - 不要依赖锁自动释放:虽然
Close()会释放,但如果中间 panic 且没 recover,defer不执行,锁就一直挂着(尤其 daemon 进程) - 跨进程场景下,必须约定锁文件路径一致,比如都用
/var/run/myapp.lock,而不是各自用os.Getwd()拼路径 - 调试时用
lsof -n | grep lock(Linux)或handle.exe -p myapp.exe(Windows)查锁是否残留,避免误判为死锁
最麻烦的从来不是“怎么加锁”,而是“谁在读、谁在写、锁住了别人会不会等太久、锁失败后要不要退避重试”。这些没法靠一个 Flock 调用解决,得结合具体业务定协议。比如日志轮转用 flock + 时间戳校验,配置热更用锁 + 版本号比对,都不是纯技术问题。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










