os.File.Lock在Go标准库中并不存在,这是开发者常见误区;标准库未封装跨平台文件锁因flock与LockFileEx行为差异大、建议性锁语义模糊,推荐使用github.com/gofrs/flock等成熟封装。

os.File.Lock 在 Go 中并不存在 —— Go 标准库的 *os.File 类型没有 Lock 方法。这是开发者常踩的第一个坑:误以为它像 C 的 flock() 或 Python 的 fcntl.flock 那样直接可用。
为什么 os.File 没有 Lock 方法
Go 标准库有意不封装平台级文件锁,因为:
- POSIX
flock()和 WindowsLockFileEx()行为差异大(比如是否继承 fork、是否跨进程生效) - 锁语义模糊:建议性锁(advisory) vs 强制性锁(mandatory),而后者在现代系统中基本不可用
- 标准库倾向暴露底层能力(如
syscall)而非抽象出易误用的高层接口
替代方案:用 syscall.Syscall 实现跨平台 flock
实际项目中,推荐使用社区验证过的封装,比如 github.com/gofrs/flock。它内部用 syscall.Flock(Unix)和 syscall.LockFileEx(Windows)实现,行为一致且可重入。
手动调用 syscall 容易出错,例如:
- 忘记在
defer中Unlock,导致死锁 - 忽略
syscall.EINTR重试逻辑 - Windows 下传错
dwFlags(比如漏掉LOCKFILE_EXCLUSIVE_LOCK)
正确用法示例:
import "github.com/gofrs/flock"
f := flock.New("/path/to/lock")
locked, err := f.TryLock()
if err != nil {
log.Fatal(err)
}
if !locked {
log.Fatal("failed to acquire lock")
}
defer f.Unlock() // 必须 defer,否则进程退出后锁可能残留
Lock 和 fcntl.F_SETLK 的关键区别
很多开发者想用 syscall.FcntlFlock 手动调用,但要注意:
-
syscall.Flock(对应flock(2))基于文件描述符,父子进程共享锁,不依赖文件路径 -
syscall.FcntlFlock(对应fcntl(2))基于 inode + 偏移范围,支持区域锁,但 Windows 不支持 - Go 的
syscall.Flock在 Windows 上是模拟的(通过命名互斥体),而syscall.FcntlFlock在 Windows 上直接返回ENOSYS
所以若需跨平台,只应依赖 syscall.Flock 封装,不要碰 FcntlFlock。
常见错误:用 os.OpenFile(O_CREATE) 代替锁
有人用 os.OpenFile(path, os.O_CREATE|os.O_EXCL, 0644) 做“锁文件”,这本质是竞态检测,不是锁:
- 创建成功 ≠ 获得锁,只是“此刻没人创建”;进程崩溃后锁文件残留,后续无法自动清理
- 没有阻塞等待机制,
TryLock逻辑要自己轮询 + sleep,浪费 CPU - 无法区分“锁被谁持有”,调试困难
真正需要协作式并发控制时,必须用系统级 advisory lock,而不是文件存在性判断。
最易被忽略的一点:锁只对显式调用 Lock 的进程有效,所有参与者必须使用同一套锁机制,且锁文件路径必须完全一致(包括符号链接解析后的绝对路径)。任何一方绕过,整个控制就失效。











