go中flock不跨进程,因锁绑定于文件描述符而非路径;独立进程各自open同一文件会获得不同fd,互不影响,需用gofrs/flock等库或锁文件方案解决。

Go 里 flock 不跨进程?先确认你用的是什么锁机制
Go 标准库的 os.File.Lock() 和 os.File.Unlock() 在 Linux/macOS 上底层调用的是 flock(2),它**是进程级的,但不是跨进程的“全局锁”**——准确说是“同一打开文件描述符的持有者之间有效”,而 fork 出的子进程会继承 fd,所以父子进程能感知;但完全独立的两个进程(各自 os.Open 同一个文件)拿到的是不同 fd,flock 互不干扰。这是最常踩的坑:你以为加了锁,其实没生效。
- 验证方式:开两个终端,分别运行
go run main.go(都对同一文件调用flock),观察是否阻塞——大概率不阻塞 - 根本原因:
flock锁绑定在 fd 上,而非文件路径;两个进程各自Open就是两个独立 fd - 替代思路:必须让多个进程竞争同一个 fd,常见做法是提前用一个守护进程 open 文件并 hold 住 fd,其他进程通过 Unix socket 或信号与之通信——太重,不推荐
用 syscall.Flock + O_RDONLY 打开文件更可靠
绕过 Go 标准库封装,直接调用系统调用,配合以只读方式打开文件,能让 flock 表现出更接近“路径级锁”的行为——因为内核对同一 inode 的只读 fd 也会统一管理锁状态(注意:这仍是内核行为,非 POSIX 保证,但在主流 Linux 发行版上稳定可用)。
fd, err := syscall.Open("/tmp/my.lock", syscall.O_RDONLY|syscall.O_CREAT, 0644)
if err != nil {
log.Fatal(err)
}
defer syscall.Close(fd)
err = syscall.Flock(fd, syscall.LOCK_EX|syscall.LOCK_NB)
if err != nil {
log.Fatal("failed to acquire lock:", err) // 如 syscall.EWOULDBLOCK
}
// 持有锁期间做业务操作
- 关键点:必须用
syscall.Open,不能用os.OpenFile,否则无法确保 fd 复用语义 -
O_RDONLY比O_RDWR更安全,避免因文件截断导致 fd 失效 -
LOCK_NB非阻塞是必须的,否则可能卡死;业务层需自己实现重试或超时 - Windows 不支持
flock,此方案仅限 Unix-like 系统
生产环境建议用 github.com/gofrs/flock
这个库封装了 syscall.Flock 的细节,并处理了清理、信号中断、临时文件等边界情况,比手写更稳妥。它内部也是基于 fd + O_RDONLY,但做了大量防御性检查。
import "github.com/gofrs/flock"
myLock := flock.New("/tmp/my.lock")
locked, err := myLock.TryLock()
if err != nil {
log.Fatal(err)
}
if !locked {
log.Fatal("could not acquire lock")
}
defer myLock.Unlock()
- 自动创建锁文件(无需预置),权限默认
0600 -
TryLock()非阻塞,Lock()会阻塞,按需选择 - 进程崩溃时锁文件不会自动释放,但下次
TryLock()会检测到 stale lock 并尝试 cleanup(依赖stat时间戳) - 注意:它不解决 NFS 场景下的锁失效问题——NFSv3 及更早版本不支持
flock,NFSv4 支持但需服务端配置开启
真正跨网络/多机器?别用文件锁,换分布式协调服务
如果“跨进程”实际指跨机器(比如 Kubernetes 多 Pod 写同一共享存储),flock 完全失效。此时文件锁已不是解决方案,而是问题本身。
- NFS/CIFS 共享卷上
flock行为不可靠,尤其高并发时容易出现假成功或死锁 - ETCD、Redis、ZooKeeper 提供原子性的 lease 或 watch 机制,才是正确解法。例如用 Redis 的
SET key value EX 30 NX实现带 TTL 的租约锁 - 哪怕只是本地多容器,也建议用 hostPath +
flock前加容器间同步屏障(如 initContainer 检查锁文件存在性),而不是依赖锁本身跨容器生效
文件锁的语义边界很窄:它只在单机、同一文件系统、非 NFS 的前提下,对“竞争同一路径的多个独立进程”才勉强可用。一旦涉及容器、虚拟化、网络存储,就该立刻切换技术栈。











