Go中无统一“文件被占用”判断标准,因跨平台语义差异大;应提取*os.SyscallError并比对syscall.ERROR_SHARING_VIOLATION、EBUSY或EAGAIN等底层错误码,而非依赖os.IsNotExist或os.IsPermission。

Go 里没有“文件是否被占用”的标准答案——因为“占用”在不同系统上语义完全不同,直接问这个,大概率会掉进错误抽象的坑里。
别用 os.IsNotExist 或 os.IsPermission 判断占用
这两个函数完全不相关:os.IsNotExist查的是路径不存在(ENOENT),os.IsPermission查的是权限不足(EACCES)。Windows 下 Excel 正在编辑一个 .xlsx 文件时,你调用 os.OpenFile(path, os.O_RDWR, 0),实际返回的是 *os.SyscallError,底层 Err 是 syscall.ERROR_SHARING_VIOLATION,既不是 ENOENT 也不是 EACCES。
常见误判现象:
- 代码里写
if os.IsNotExist(err) { ... },结果文件明明被锁着,却走到了“不存在”分支 - 用
strings.Contains(err.Error(), "denied")匹配,但在中文 Windows 或容器里,错误消息可能被本地化或截断
真正该检查的是底层 syscall 错误码
跨平台判断“可能被占用”,唯一靠谱的方式是提取 *os.SyscallError 并比对具体错误值:
var se *os.SyscallErrorif errors.As(err, &se) && (se.Err == syscall.ERROR_SHARING_VIOLATION || se.Err == syscall.EBUSY || se.Err == syscall.EAGAIN) { // 文件很可能被其他进程持有句柄或加了锁}
注意点:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
syscall.EBUSY在 Linux 常见于设备忙(如 umount 中的挂载点),普通文件很少见;但某些 NFS 或 fuse 文件系统会返回它 -
syscall.EAGAIN多出现在非阻塞 flock 失败时,不是“被占用”,而是“此刻拿不到锁”,需重试 - Windows 的
ERROR_SHARING_VIOLATION是最典型的“被占用”信号,比如文件正被 Word、Excel、记事本以独占模式打开
os.OpenFile 的 flag 决定你能看到什么错误
打开方式直接影响错误类型:
- 用
os.O_RDONLY打开一个被 Excel 锁住的文件 → 通常成功(只读不冲突) - 用
os.O_RDWR或os.O_WRONLY→ 很可能触发ERROR_SHARING_VIOLATION - 用
os.O_CREATE | os.O_TRUNC→ 可能直接报ERROR_ACCESS_DENIED,尤其在 Windows 上,因为系统禁止覆盖正在被程序打开的文件
所以,如果你的业务必须写入,就别先用只读试探——那会掩盖真实冲突。直接以写模式打开,再处理对应 syscall 错误。
flock 加锁失败 ≠ 文件被占用
很多人用 unix.Flock(fd, unix.LOCK_EX|unix.LOCK_NB) 返回 unix.EWOULDBLOCK 就认为“文件被占”,这是错的。它只表示“当前没有拿到锁”,原因可能是:
- 另一个进程正持有该文件的排他锁(真占用)
- 另一个进程持有共享锁,而你试图加排他锁(flock 兼容性规则)
- 文件根本没以
os.O_RDWR打开(导致EBADF,但被误判为锁冲突)
更关键的是:flock 是劝告锁(advisory lock),它不阻止任何读写操作——只要对方不调用 flock,你的锁形同虚设。所谓“被占用”,其实是业务约定,不是内核强制。
真正的难点从来不在怎么检测,而在于:你检测到“被占用”之后,到底想做什么?重试?排队?降级为只读?还是直接报错?这些决策比技术判断重要得多。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










