“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 是不够的,得在目标部署环境里实际跑并发压测。










