os.OpenFile 模式错误会导致 EBADF 或 EACCES 等错误而非 panic;并发读写同一 *os.File 会引发未定义行为,需加锁;探测占用应通过试探打开或系统级锁,而非字符串匹配错误。

os.OpenFile 用错模式会直接报 conflict 错误
Go 里没有“读写冲突”这种运行时异常,所谓冲突其实是 os.OpenFile 指定的打开模式与文件当前状态或操作系统限制不兼容导致的 error。最常见的是用 os.O_RDONLY 打开后尝试写,或用 os.O_WRONLY 打开后调用 Read —— 此时不会 panic,但 Write 或 Read 返回的 err 通常是 EBADF(bad file descriptor),在 Linux/macOS 上表现为 "bad file descriptor"。
-
os.O_RDONLY打开的文件句柄不能写;os.O_WRONLY的不能读;必须用os.O_RDWR才支持双向操作 - 如果文件已被其他进程以独占方式打开(如 Windows 上用
CREATE_ALWAYS+FILE_SHARE_NONE),os.OpenFile会直接失败,错误为"Access is denied"(Windows)或"text file busy"(Linux 下执行中二进制被覆盖) - 不要依赖
err.Error()字符串匹配判断冲突,应使用errors.Is(err, syscall.EBADF)或errors.Is(err, syscall.EACCES)做跨平台识别
多个 goroutine 并发读写同一 *os.File 是真冲突
Go 不禁止并发调用同一个 *os.File 的 Read 或 Write,但结果未定义:可能数据交错、偏移错乱、甚至 io.ErrUnexpectedEOF。这不是 error,而是竞态行为 —— go run -race 能检测到,但仅限于实际执行路径。
- 并发读写前必须加锁,推荐用
sync.Mutex包裹file.Read/file.Write调用,而不是对整个文件句柄做读写分离 -
bufio.Reader和bufio.Writer本身不带同步,也不能解决底层*os.File的并发问题 - 若需高并发读,优先考虑打开多个只读句柄(
os.Open多次),而非复用一个*os.File并加读锁
检测文件是否正被其他进程占用
Go 标准库不提供跨平台“文件锁定探测”,但可通过 os.OpenFile 的试探性打开 + 错误类型判断来间接推断。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 用
os.O_RDWR | os.O_CREATE | os.O_EXCL尝试打开 —— 若失败且os.IsExist(err)为 true,说明文件已存在且可能被占用(尤其 Windows) - Linux 下可尝试
syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB),失败则大概率被占用;但 Windows 不支持flock,需用syscall.LockFileEx - 更稳妥的做法是接受“打开失败即不可用”,按业务降级:比如日志轮转时跳过被占用的旧文件,而不是死等或报错退出
Write 返回 n
file.Write([]byte) 返回 n, err,当 err == nil 但 n 时,不是冲突,而是底层写入被截断。这在磁盘满、NFS 服务中断、或 <code>/dev/full 等场景真实发生。
立即学习“go语言免费学习笔记(深入)”;
- 永远不要忽略
n—— 即使err == nil,也要检查是否写全:if n != len(p) { return io.ErrShortWrite } - 关键数据写入后务必调用
file.Sync(),并检查其返回的err;Sync()失败(如ENOSPC)才是真正意义上的“写入失败” - 避免用
ioutil.WriteFile或os.WriteFile替代手动控制流程,它们内部不校验n,也不暴露Sync结果
*os.File 且只在业务逻辑里加锁,却忘了 file.Seek 是共享状态 —— 一次 Seek 会影响所有后续 Read/Write 的起始位置。这点比锁本身更容易出错。

















