go标准库无跨平台flock实现,因windows不支持syscall.flock且类unix系统存在fd复用、nfs不可靠等问题;推荐用github.com/kevinburke/lockfile基于原子link实现跨平台文件锁。

Go 标准库没有跨平台 flock 实现,直接调用 syscall.Flock 会失败或行为不一致 —— 别试图封装 syscall 层做“统一”,得换思路。
为什么 syscall.Flock 不能跨平台用
Linux/macOS 支持 flock(2) 系统调用,但 Windows 完全不支持;syscall.Flock 在 Windows 上返回 ENOSYS 或 panic。即使在类 Unix 系统上,flock 锁还依赖文件描述符生命周期(fork 后子进程继承锁、close 后自动释放),而 Go 的 runtime 可能复用 fd 或提前关闭,导致锁意外丢失。
- macOS 上
flock对 NFS 挂载点不可靠 - Linux 容器中若挂载为
noexec或nosuid,不影响flock,但若用overlayfs且底层为 ext4,锁仍有效;若底层是 xfs 并启用了inode64,可能有极小概率出现锁失效(罕见但存在) - Go 1.21+ 中
syscall.Flock已标记为 deprecated,官方建议用更高层抽象
用 github.com/kevinburke/lockfile 做轻量替代
它不依赖系统级 flock,而是基于原子文件写入 + os.Link(硬链接)实现互斥,天然跨平台,且对 NFS 友好(只要目标目录支持硬链接)。
- 创建锁:调用
lockfile.New("path/to/lock"),再l.Lock()—— 成功则返回*lockfile.Lock,失败返回 error - 锁文件内容无关紧要,本质是利用
link(old, new)的原子性:仅当new不存在时才成功,否则返回syscall.EEXIST - 解锁必须显式调用
l.Unlock();程序 crash 时锁不会自动清理,需靠上层加超时或定期 sweep(见下一点) - 不阻塞:默认非阻塞,想等待可用加
lockfile.WithTimeout(5 * time.Second)
l := lockfile.New("myapp.lock")
err := l.Lock()
if err != nil {
log.Fatal(err) // 可能是 "failed to create lock: file exists"
}
defer l.Unlock() // 注意:defer 不防 panic,生产环境建议用带 recover 的 wrapper
自己实现带租约的文件锁(适合长时任务)
如果任务可能运行数小时、且不能容忍残留锁(比如 cron job 重启后卡住),就该用「带时间戳的锁文件 + 租约」模式,而非依赖内核锁语义。
- 锁文件内容写入当前 PID + UNIX 时间戳(如
"12345 1717028340") - 获取锁前先读旧锁文件:若存在且时间戳 > N 秒(比如 30s),认为持有者已死,允许覆盖(注意用
os.WriteFile+os.Rename替代直接os.Remove+os.Create,避免竞态) - 释放锁时只删自己的 PID 对应的文件(或清空内容),不删整个锁文件,避免误删他人锁
- 推荐配合
os.Getpid()和time.Now().Unix(),不要用time.Now().UTC().UnixNano()——纳秒级时间戳在不同机器上无法比大小
这类逻辑已有成熟封装,例如 github.com/hashicorp/errwrap 不适用,但 github.com/gofrs/flock 其实也只做了 syscall 封装,**真正靠谱的是 github.com/bsm/redis-lock(用 Redis)或自己用本地文件 + 租约** —— 只要你的场景不需要分布式,纯本地文件就够了。
Windows 下尤其注意路径和权限
在 Windows 上,即使是「跨平台」锁库,也可能因路径格式或权限失败:
- 用
filepath.Join("dir", "lock")而不是字符串拼接,避免"dir\lock"和"dir/lock"混用 - Windows Defender 或组策略可能拦截临时目录下的锁文件创建(尤其
%TEMP%),建议锁文件放在应用数据目录:os.UserCacheDir()或os.UserConfigDir() - 以管理员身份运行的进程无法被普通用户进程锁竞争 —— 若工具需兼容两种权限上下文,锁路径必须区分(比如加
-admin后缀)
最麻烦的不是锁本身,而是锁失效后的恢复逻辑:你得定义清楚「什么算锁失效」「谁负责清理」「清理是否安全」——这些没法靠一个 Flock 调用解决。











