go 中 flock 文件锁本质是调用 linux/unix 的 flock(2) 系统调用,非语言内置功能,windows 不支持;需操作同一文件描述符,建议使用 github.com/gofrs/flock 包。

Go 里用 flock 做文件锁,本质是调用系统 syscall,不是语言内置功能
Go 标准库没有直接叫 flock 的函数,得靠 syscall 或封装好的第三方包(比如 github.com/gofrs/flock)。底层其实是调用 Linux/Unix 的 flock(2) 系统调用,Windows 不支持——这点很多人踩坑后才反应过来。
常见错误现象:os.OpenFile 打开文件后直接传给 flock 调用失败,报 invalid argument;或者锁住了但另一个进程照样能写,发现是因为没对同一个文件描述符加锁,而是各自打开了新 fd。
- 必须用
syscall.Flock或兼容包操作同一个打开的文件描述符(*os.File.Fd()),不能只靠文件路径 - 加锁前要确保文件已以读写模式打开(哪怕只读也要带
os.O_RDWR,否则flock可能拒绝) -
github.com/gofrs/flock是目前最稳的选择:自动处理 fd 复制、跨 fork 行为、释放逻辑,比手撸syscall少掉三成坑
flock 是建议性锁,不拦得住暴力 open+write
flock 锁的是文件描述符,不是文件本身。它不阻止其他进程用 open(2) 打开同一文件,也不拦截 write(2) —— 除非对方也主动调用 flock 并检查返回值。换句话说,它是“合作式”锁,不是强制访问控制。
使用场景很明确:多个 Go 进程(或混用 shell 脚本)需要协调对某个配置文件、PID 文件、临时状态文件的独占操作,比如定时任务避免重复执行。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 如果你的服务里混用了 Python/Shell 写同一个文件,它们也得调
flock,否则锁形同虚设 -
flock不影响 mmap、readlink、stat等元数据操作,只约束显式加锁行为 - 不要指望它防住 rm / mv / truncate —— 这些操作绕过锁机制,得靠文件权限或目录级保护
用 gofrs/flock 加锁的最小可靠写法
别自己封装 syscall.Flock,除非你真清楚 FD_CLOEXEC、fork 后锁继承、信号中断重试这些细节。直接上 gofrs/flock,它把边界情况都收进去了。
import "github.com/gofrs/flock"
f := flock.New("/tmp/myapp.lock")
locked, err := f.TryLock()
if err != nil {
log.Fatal(err)
}
if !locked {
log.Fatal("another instance is running")
}
defer f.Unlock() // 注意:Unlock 不报错,但必须调
-
TryLock()非阻塞,适合启动时快速判断;要用阻塞版就换Lock() -
Unlock()在进程退出前必须调,否则锁可能残留(虽然内核通常在 fd 关闭时自动释放,但依赖这个不稳妥) - 锁文件路径尽量用绝对路径,避免工作目录不同导致锁文件错位
- 别把锁文件放在 NFS 上——
flock在多数 NFS 实现中不可靠,会静默失效
为什么不用 os.Chmod 或临时文件做替代?
有人试过用 os.Rename 原子改名、或 os.Chmod 改权限来模拟互斥,结果在高并发下出现竞态:两个进程同时 Stat 发现文件不存在,然后都去创建,锁就漏了。
性能和语义上都不如 flock:系统调用级锁延迟低、内核保证原子性;而文件操作涉及磁盘 I/O、缓存、POSIX 语义模糊点(比如 rename 在某些文件系统上非原子)。
-
flock加锁/解锁是微秒级,touch + chmod至少毫秒级,还多两次 syscalls - 临时文件方案在容器环境尤其危险:/tmp 可能被挂载为 tmpfs,重启即丢,锁状态无法持久
- 真正难搞的是跨主机协调——这时候
flock本来就无能为力,该上 etcd/Redis 分布式锁了
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










