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