flock是linux/macos下最轻量的跨进程文件互斥方案,windows需用lockfileex,分布式环境(k8s/nfs/多机)下完全失效,必须改用redis或etcd等中心化协调服务。

flock 是最直接、最轻量的跨进程文件互斥方案,但仅限 Linux/macOS;Windows 必须用 syscall.LockFileEx;分布式环境(K8s/NFS/多机)下 flock 完全失效,必须换 Redis 或 etcd。
Linux/macOS 下用 syscall.Flock 加独占锁写文件
flock 锁是 advisory lock(建议性锁),不拦截裸 write,只靠所有参与者主动调用才生效。只要统一走这套流程,就能防止并发写乱序或覆盖。
- 必须用
os.O_WRONLY | os.O_CREATE(或os.O_RDWR)打开文件,不能只读打开后加LOCK_EX - 加锁前要确保文件已存在或已创建(推荐提前
touch /var/run/myapp.lock),否则os.OpenFile创建新文件会生成新 inode,旧锁无效 - 锁在
file.Close()时自动释放,不手动syscall.Flock(fd, syscall.LOCK_UN)也行,但显式解锁更可控 - 不要省略错误检查:
syscall.EWOULDBLOCK表示已被其他进程持独占锁,不是异常,可重试或跳过
file, err := os.OpenFile("data.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
panic(err)
}
defer file.Close()
<p>err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK<em>NB)
if err != nil {
if errors.Is(err, syscall.EWOULDBLOCK) {
log.Println("锁被占用,跳过写入")
return
}
panic(err)
}
</em>, _ = file.WriteString("append line\n")
</p>
Windows 上必须用 LockFileEx,flock 不可用
Windows 没有flock 系统调用,syscall.Flock 在 Windows 上始终返回 ENOSYS。必须用 syscall.LockFileEx,且注意:
- 文件需以
os.O_RDWR打开,只读或只写句柄无法加锁 -
LockFileEx的 flag 要传syscall.LOCKFILE_EXCLUSIVE_LOCK(写锁)或syscall.LOCKFILE_SHARED_LOCK(读锁) - 第二个参数是超时控制,传
0表示非阻塞,失败立即返回;传syscall.INFINITE会永久等待 - 解锁必须调用
UnlockFileEx,不会随Close自动释放
为什么 gofrs/flock 库在跨平台项目里常踩坑
gofrs/flock 封装了 flock 和 LockFileEx,但默认行为容易误导:
- 它的
TryLock()在 Windows 下仍可能因句柄权限不足静默失败,建议打开文件时显式加syscall.FILE_FLAG_OVERLAPPED - 它内部用临时文件做锁路径,若多个进程各自调用
flock.New("x.lock")且未共享同一文件实体(比如没提前创建),实际锁的是不同 inode - 在 NFS 挂载点上,即使 Linux 主机,
flock也可能被挂载选项(如nolock)禁用,gofrs/flock不报错但锁失效
分布式场景下文件锁根本不可用
K8s 多 Pod、NFS 共享卷、混合 OS 集群中,flock 和 LockFileEx 都只作用于本机 fd 视图,彼此不可见。
- 两个 Pod 同时执行
os.OpenFile(..., os.O_CREATE|os.O_EXCL),在 NFS 上可能都成功——O_EXCL不保证原子性 - 即使所有 Pod 都调用
flock,它们锁的是各自 mount namespace 下的文件副本,不是同一个 inode - 真实可行的方案只有中心化协调:Redis 用
SET key val NX PX 30000+ Lua 续期,或 etcd 用CAS + Lease
真正难的不是“怎么加锁”,而是判断当前部署环境是否支持文件锁——inode 是否一致、挂载选项是否允许、是否存在跨节点调度。这些细节一旦漏掉,锁就形同虚设。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











