flock 在 go 中无法跨平台,windows 不支持该系统调用;推荐用 os.openfile 配合 o_create|o_excl 原子创建锁文件,或使用 github.com/nightlyone/lockfile 库实现跨平台文件锁。

flock 在 Go 中无法跨平台——Windows 根本不支持 flock 系统调用,直接调用会 panic 或静默失败。 你写的 Linux/macOS 上能跑的 flock 工具,在 Windows 上要么报 operation not supported,要么根本锁不住(尤其在 WSL2 和 Windows 原生混合部署时极易误判)。
Go 标准库没有跨平台 flock 封装
标准库 os.File 的 Chmod、Sync 都是跨平台的,但 syscall.Flock 是纯 Unix 专属:Linux/macOS 走 sys_flock,Windows 直接返回 ENOSYS。Go 官方明确不计划补全 Windows 支持——因为 Windows 文件锁语义完全不同(基于 LockFileEx,作用域、释放时机、共享/独占模式都不可对齐)。
- 别依赖
golang.org/x/sys/unix.Flock:它只是 syscall 的封装,同样在 Windows 编译失败或 runtime panic - 别用
build tags分平台写两套逻辑:业务代码会迅速失控,且 Windows 上模拟 flock 行为(比如靠文件存在性)极易在并发重启、NFS、杀进程未清理等场景下死锁或漏锁 - 真实需求往往不是“必须用 flock”,而是“防止同一资源被多个实例同时操作”——这个目标可以用更可靠的方式达成
用文件原子写入 + stat 检查替代 flock
这是最轻量、最可控的跨平台方案:不依赖系统锁原语,靠文件系统“创建文件是否成功”这一原子操作来选举持有者。核心是 os.OpenFile 配合 os.O_CREATE | os.O_EXCL | os.O_WRONLY。
lockFile := "/tmp/myapp.lock"
f, err := os.OpenFile(lockFile, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
if err != nil {
if os.IsExist(err) {
log.Fatal("another instance is running")
}
log.Fatal("failed to create lock file:", err)
}
// 成功拿到锁:写入 PID 并保持文件打开
fmt.Fprintf(f, "%d", os.Getpid())
// 注意:不要 f.Close()!进程退出时内核自动释放
- 必须保持
*os.File句柄打开——关闭即释放锁,不能只写完就关 - 路径要选本地磁盘(避开 NFS、某些容器 volume),否则
O_EXCL可能不原子 - 加个 defer
os.Remove(lockFile)不安全:进程 crash 时不会触发,建议配合 signal 处理或启动时先清理陈旧锁文件
需要真正互斥且容忍复杂性?用 github.com/nightlyone/lockfile
这个库是目前最成熟的跨平台文件锁实现:Linux/macOS 调 flock,Windows 调 LockFileEx,还处理了 pid 文件校验、stale lock 清理、可配置超时等细节。
- 安装:
go get github.com/nightlyone/lockfile - 基础用法:
l, err := lockfile.New("/tmp/myapp.lock") if err != nil { log.Fatal(err) } if err := l.Lock(); err != nil { log.Fatal("cannot acquire lock:", err) } defer l.Unlock() // 进程退出前务必调用 - 注意它默认不写 PID,如需防僵尸锁,得自己读写 lock 文件内容;另外它的
Unlock()在 Windows 上可能因句柄泄露失败,建议搭配runtime.LockOSThread避免 goroutine 迁移导致句柄丢失
跨平台锁真正的难点不在代码怎么写,而在于“锁什么”和“锁多久”。用文件锁保护的是临界资源,但资源本身(比如数据库表、远程 API 端点)往往有更上层的协调机制。与其花力气让 flock 勉强跨平台,不如先确认:这个锁是不是真的必须落在文件系统层面?











