syscall.Flock不能跨平台用,因Windows无POSIX flock(2)系统调用,编译即报undefined错误;且它是建议性锁,依赖所有进程主动调用,绕过即失效,Linux/macOS上还受fd生命周期、NFS、inode一致性等限制。

syscall.Flock 为什么不能直接跨平台用
它根本不是 Go 的抽象封装,而是对 POSIX flock(2) 系统调用的直通——Windows 没这个系统调用,编译时就报 undefined: syscall.Flock。这不是兼容性问题,是底层缺失。Linux/macOS 上能跑,不代表代码可移植;哪怕加了 //go:build !windows,也只解决编译,不解决语义差异。
更关键的是:它只在所有进程都主动调用 syscall.Flock 时才生效。只要有一个进程(比如运维脚本、crontab 里的 shell 命令)绕过锁直接 echo >> data.json,锁就完全失效——这是建议性锁的本质,不是 bug。
- Linux/macOS 下必须用
os.O_CREATE | os.O_RDWR打开文件,os.O_RDONLY在部分内核版本下会拒绝LOCK_EX - 锁绑定的是
fd,不是路径:两个进程各自os.OpenFile("x.lock", ...),得到两个独立 fd,互不感知 - 进程退出时内核自动释放锁,但显式调用
syscall.Flock(fd, syscall.LOCK_UN)更利于调试和资源清理
非阻塞加锁必须带 LOCK_NB,否则 goroutine 会卡死
默认 syscall.Flock(fd, syscall.LOCK_EX) 是阻塞的,一旦锁被占,当前 goroutine 就挂起,无法响应超时、取消或降级逻辑。生产环境几乎从不直接用阻塞模式。
正确写法是组合 syscall.LOCK_EX | syscall.LOCK_NB,失败时检查错误是否为 syscall.EWOULDBLOCK——这表示锁正被占用,属于预期行为,不是系统异常。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 遇到
syscall.EWOULDBLOCK后,可选择:time.Sleep(50 * time.Millisecond)后重试,或直接返回错误、写入临时文件、走备用路径 - 不要用
time.After包裹整个syscall.Flock调用——它不支持超时参数,只能靠外层控制重试逻辑 -
defer f.Close()必须放在加锁成功之后,否则可能提前关闭 fd 导致锁释放
共享锁 LOCK_SH 和独占锁 LOCK_EX 混用要小心
LOCK_SH 允许多个进程共存,但会阻塞后续任何 LOCK_EX 请求;只要有任意一个 LOCK_EX 存在,所有新 LOCK_SH 和 LOCK_EX 都会失败(非阻塞时返回 EWOULDBLOCK)。
常见误区是给“读配置”加 LOCK_SH——读操作本身不修改文件,加锁纯属冗余,反而增加系统调用开销,还可能被其他进程的 LOCK_EX 意外阻塞。
- 真正需要
LOCK_SH的场景极少,比如多个进程协同生成只读缓存,且需确保期间无人写入 - 没有锁升级机制:不能从
LOCK_SH直接升为LOCK_EX,必须先syscall.Flock(fd, syscall.LOCK_UN),再重试LOCK_EX - 所有进程必须打开**同一个 inode**:符号链接、NFS 不同挂载点、反复
os.Create都会导致锁失效
Windows 下别硬扛,换 LockFileEx 或 gofrs/flock
Windows 完全不支持 flock(2),强行适配只会浪费时间。可靠方案只有两个:
- 用
golang.org/x/sys/windows.LockFileEx:支持字节范围锁、超时、可中断,是 Windows 原生推荐方式 - 用
github.com/gofrs/flock:内部按平台 dispatch,Linux 走syscall.Flock,Windows 走LockFileEx,还处理了 fork 后 fd 复制、信号中断等边界 - 底线方案是锁文件 +
os.O_CREATE | os.O_EXCL:利用原子创建语义,行为跨平台一致,Nginx/systemd 都这么用,但要注意它只解决“启动抢占”,不保护运行中文件内容
真正难的不是选哪个 API,而是统一所有进程(包括非 Go 进程)的锁协议——如果一部分用 flock,一部分用 O_EXCL,它们互不感知,锁就形同虚设。

















