go中无跨平台文件占用检测函数,应直接尝试加锁:linux/macos用syscall.flock加非阻塞独占锁,windows用golang.org/x/sys/windows.lockfileex,失败即处理冲突,避免check-then-act竞态。

Go 中没有直接检测“文件是否被占用”的标准函数
操作系统层面不存在一个跨平台、原子、可靠的 IsFileLocked 函数。所谓“被占用”,本身语义模糊:是被其他进程以写方式打开?还是已被加了 flock?或是 Windows 下被 LockFileEx 锁定了某段范围?不同系统、不同锁机制,表现完全不同。试图用统一方式判断,大概率踩坑。
Linux/macOS 下尝试获取非阻塞独占锁是最接近的方案
本质是“试锁”,而非“查状态”。用 syscall.Flock 加 syscall.LOCK_EX | syscall.LOCK_NB,失败且错误为 syscall.EWOULDBLOCK 或 syscall.EAGAIN,可认为当前有进程持有该文件的排他锁(或文件被以不兼容方式打开)。
-
os.OpenFile(path, os.O_RDWR, 0)必须先打开文件,否则无法获得 fd - 不能对只读打开的文件加
LOCK_EX,会返回syscall.EBADF - 即使成功加了锁,也只说明“此刻没被其他 flock 占用”,不代表文件没被其他进程用
open(O_WRONLY)打开后未加锁——因为flock是建议性锁,不强制拦截 - 子进程会继承父进程的 fd 和锁状态,
fork后需注意锁是否意外延续
Windows 下必须用 golang.org/x/sys/windows 调用 LockFileEx
syscall.Flock 在 Windows 完全不可用,编译会报 undefined: syscall.Flock。必须走 Win32 API 的 LockFileEx,且只能实现独占锁(无共享锁),参数需设 lockfile_exclusive_lock | lockfile_fail_immediately 才能模拟非阻塞行为。
- 不能依赖
os.File.Lock()方法做跨平台检测——它在 Linux/macOS 底层调fcntl,在 Windows 调LockFileEx,但错误码和语义不一致 -
LockFileEx锁的是字节范围,锁定整个文件需传0偏移 +0长度(表示“到 EOF”) - Go 1.21+ 中
golang.org/x/sys/windows已稳定,无需手写 cgo;但需确保GOOS=windows构建 - 即使
LockFileEx失败,也不能断定“文件被占用”——可能是权限不足、路径不存在、或句柄无效
真正可靠的做法是放弃“检测”,改用“抢占式加锁”
所有基于“先检查再操作”的逻辑(check-then-act)在并发环境下都存在竞态窗口。正确姿势是:直接尝试加锁,成功则操作,失败则处理冲突(重试/报错/降级)。这也是 lobsterlock 等库的设计哲学——它封装了底层差异,对外只暴露 Lock() 和 Unlock(),内部在各平台用最稳妥的方式抢占。
- 不要写
if !isLocked() { doWrite() },而应写if err := lock.Lock(); err != nil { /* handle */ } else { defer lock.Unlock(); doWrite() } - 锁对象生命周期要和业务逻辑对齐,避免 goroutine panic 或提前 exit 导致
defer Unlock()不执行 - 文件锁不是万能的:它不阻止 mmap 写入、不拦截 unlink/rename、也无法跨 NFS 可靠工作
最常被忽略的一点:文件锁只是协调手段,不是安全边界。如果某个进程根本不调用锁,它依然能读写文件——这正是“建议性锁”的本质。真正的保护,得靠权限控制、进程隔离或更高层的协议设计。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











