go语言无跨平台文件锁原语,须用平台特化实现:linux/macos避免已弃用的syscall.flock,windows用os.createfile+file_share_none;推荐接口+构建标签隔离实现,或直接使用lobsterlock库。

Go 语言没有跨平台文件锁原语,别硬套 syscall.Flock
直接用 syscall.Flock 写跨平台代码,Windows 上编译失败或 panic 是必然结果。这不是配置问题,而是 Windows 根本不提供该系统调用。Linux/macOS 虽支持,但 syscall.Flock 在 Go 1.21+ 已被标记为 deprecated,官方明确建议避免直接使用。更麻烦的是,它的锁绑定到文件描述符(fd),而 Go 运行时可能复用 fd 或提前关闭,导致锁状态意外丢失——你“加了锁”,其实早就释放了。
os.CreateFile + FILE_SHARE_NONE 是 Windows 的等价替代
Windows 上实现“独占打开即加锁”的语义,核心是调用 os.CreateFile(实际是 windows.CreateFile)并设置 dwShareMode = 0,也就是 FILE_SHARE_NONE。只要另一个进程已经以同样方式打开了同一路径,这次调用就会失败,返回 ERROR_SHARING_VIOLATION。这和 Unix 下 flock(fd, LOCK_EX|LOCK_NB) 失败返回 EWOULDBLOCK 的语义一致。
- 必须用绝对路径:相对路径在多进程场景下极易因工作目录不同导致锁错文件
- 父目录必须存在:
os.CreateFile不会自动创建父目录,os.MkdirAll需提前调用 -
Unlock()必须关闭对应*os.File:Windows 严格依赖句柄关闭释放锁,漏掉这步等于锁永远不释放
用接口 + 构建标签(build tag)隔离平台实现
不要在同一个文件里写 if runtime.GOOS == "windows",那会让测试难覆盖、逻辑割裂、错误处理不一致。标准做法是定义统一接口,再用构建标签分发实现:
例如定义 TryLock(path string) (bool, error) 接口,在 lock_unix.go 文件顶部加 //go:build !windows,在 lock_windows.go 顶部加 //go:build windows。Go 编译器会自动按目标平台选择对应文件,上层代码完全无感。
- 每个实现都应持有自己的
*os.File:避免 fd 复用导致锁状态混乱 - 锁文件权限需显式设为
0600:防止其他用户干扰,尤其在多用户环境 - 错误类型要统一:比如自定义
ErrLockUnavailable,方便上层用errors.Is(err, ErrLockUnavailable)判断
第三方库如 github.com/martyb/lobsterlock 更适合快速落地
如果你不需要定制超时、租约或清理逻辑,直接用 lobsterlock 是最省心的选择。它用原子 os.Link 实现,天然跨平台、对 NFS 友好、接口像 sync.Mutex 一样直觉——Lock() / Unlock(),不用操心路径、fd、平台差异。
- 默认非阻塞:
Lock()失败立即返回 error,符合“尝试-失败-跳过”场景 - 锁文件内容无关紧要:本质是利用硬链接的原子性,
link("tmp", "lock")成功即获锁 - 崩溃后锁不会自动释放:必须靠上层加定期 sweep 或带时间戳的租约机制,这点容易忽略
真正难的不是写锁,而是决定什么时候该用内核级锁(flock/CreateFile)、什么时候该用文件系统级原子操作(link)、以及锁失效后如何安全降级或重试——这些决策点,比语法本身更消耗工程判断力。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











