跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

Golang错误处理与文件锁并发_处理Resource Temporarily Unavailable

“Resource Temporarily Unavailable”是flock在非阻塞模式(LOCK_NB)下抢锁失败的正常返回,对应errno EAGAIN(11),非Go bug;需用重试逻辑或可靠库(如lockfile)处理,并注意NFS/容器环境限制。 为什么
flock
在 Go 里会报 “Resource Temporarily Unavailable” 这不是 Go 的 bug,是底层
flock(2)
系统调用在非阻塞模式下抢锁失败的正常返回。Go 的
os.File.Fd()
+
syscall.Flock()
组合如果用了
syscall.LOCK_NB
,而锁已被占用,就会直接触发这个错误。 常见场景:多个 goroutine 并发尝试对同一文件加写锁;或一个进程崩溃后没释放锁,残留的锁被内核持有(尤其在 NFS 或某些容器文件系统上)。
syscall.Flock(fd, syscall.LOCK_EX|syscall.LOCK_NB)
是罪魁祸首 —— 加了
LOCK_NB
就必然可能返回该错误 别误以为是文件被其他进程“打开着”,
flock
是 advisory lock,不依赖文件是否打开,只看是否有其他
flock
持有者 Linux 上该错误对应 errno
EAGAIN
(值 11),不是
EBUSY
;Go 的
errors.Is(err, syscall.EAGAIN)
可靠判断 Go 里安全用
flock
的三步实操 别自己裸调
syscall.Flock
。用封装好的库或手动补全重试逻辑 —— 核心是把“非阻塞失败”转为“可控等待”或“明确放弃”。 优先用
github.com/nightlyone/lockfile
:它默认带简单重试(100ms 间隔 × 10 次),且自动处理
EAGAIN
ENOLCK
若必须手写,用
for
循环 +
time.Sleep
:每次失败后 sleep 5–50ms,总超时控制在 1–3 秒内,避免卡死 加锁前先
os.OpenFile(path, os.O_CREATE|os.O_RDONLY, 0644)
确保文件存在且可访问 —— 否则
flock
可能因
ENOENT
或权限问题提前失败,掩盖真实锁问题
syscall.Flock
参数选错导致的隐形坑 同一个 fd 多次调用
Flock
不会报错,但行为取决于 flag 组合。最常踩的是锁类型和阻塞标志混用不当。 立即学习 “ go语言免费学习笔记(深入) ”;
syscall.LOCK_SH | syscall.LOCK_NB
:非阻塞共享锁 —— 多个 reader 可共存,但任一 writer 存在就失败
syscall.LOCK_EX | syscall.LOCK_NB
:非阻塞独占锁 —— 只要有人持任何锁(sh/ex),就立刻
EAGAIN
漏掉
syscall.LOCK_UN
:进程退出时不显式解锁,锁会随 fd 关闭自动释放,但若 fd 被 dup 或子进程继承,锁可能意外延续 在 fork 后调用
Flock
:子进程会继承父进程的锁状态,但锁是 per-process 的 —— 父子不能互相感知对方加的锁,容易误判 容器与 NFS 环境下
flock
失效的真实原因 很多线上服务跑在 Kubernetes 或 Docker 里,一上 NFS 就发现
flock
像没用 —— 不是 Go 写错了,是底层不支持。 NFS v3 默认不支持
flock
,v4 部分实现支持但需服务端开启
nfsd
lockd
模块,且客户端 mount 时加
noac
actimeo=0
Kubernetes
emptyDir
hostPath
卷在某些 runtime(如 containerd + overlayfs)中,
flock
行为可能不一致,建议用
/tmp
或本地盘做锁文件 替代方案更可靠:用分布式锁(如 Redis
SET key val NX PX 30000
)或基于文件原子性的方案(
os.Rename
临时文件 + 检查是否存在) 真正麻烦的从来不是怎么加锁,而是怎么确认锁在当前环境里真的起作用 —— 光看 error 是不够的,得在目标部署环境里实际跑并发压测。

相关文章