flock() 易因未用 fopen 打开文件或模式错误而返回 false;必须传资源而非路径,写场景避免 'r' 模式,推荐 'c+' 或 'a+';LOCK_EX 排他、LOCK_SH 共享,需按并发模型选择;非阻塞需加 LOCK_NB 并合理重试;务必 try-finally 解锁兜底。

flock() 不是“学不会”的东西,而是容易在第一次用错就以为它失效——它本身不难,但关键动作漏掉一个,锁就等于没加。
为什么 flock() 总是返回 false
最常见原因是:没先用 fopen() 打开文件句柄,或打开了但模式不对。
-
flock()只接受资源(resource)类型参数,不能传路径字符串,比如flock('/tmp/lock', LOCK_EX)必然失败 - 写场景下别用
'r'模式打开——fopen('log.txt', 'r')后调flock($fp, LOCK_EX)在部分系统上会静默失败;改用'c+'(创建+读写,不截断)或'a+'(追加+读)更稳妥 - 如果只是当信号量用(不存数据),用
fopen('/tmp/app.lock', 'c')足够轻量,不用写内容
LOCK_EX 和 LOCK_SH 到底怎么选
不是“读用 SH、写用 EX”就完事,得看并发模型:
-
LOCK_EX是排他锁:只要一个进程持有,其他所有LOCK_EX或LOCK_SH都会被阻塞。适合写入、更新、删除等修改操作 -
LOCK_SH是共享锁:多个进程可同时持有,但会阻止任何LOCK_EX。适合只读缓存加载、配置预热等“读多写少”场景 - 两者不能共存——
LOCK_SH持有时,LOCK_EX会等;反之亦然。别指望“读锁不挡读锁”就能高并发读,它只防写,不解决读放大问题
非阻塞锁怎么写才不翻车
默认 flock($fp, LOCK_EX) 会卡住直到拿到锁,Web 请求里这等于主动拒绝服务。必须加 LOCK_NB:
- 正确写法:
flock($fp, LOCK_EX | LOCK_NB),注意是位运算|,不是逗号或数组 - 返回
false不代表出错,只代表“此刻拿不到”,你要自己决定后续逻辑:重试?降级写本地队列?返回 429? - 别写无限循环重试:
while (!flock(...)) { usleep(10000); }容易积压。建议最多 3 次尝试,间隔用指数退避(10ms → 30ms → 100ms) - 每次调用后都必须检查返回值,
flock()失败时句柄仍有效,但锁没加上——后面所有操作都在裸奔
解锁和关闭的顺序与异常兜底
脚本结束时 PHP 确实会自动释放锁,但这不是安全依据:
立即学习“PHP免费学习笔记(深入)”;
-
exit、未捕获异常、fastcgi_finish_request()后续异步逻辑、超时中断,都可能导致flock($fp, LOCK_UN)来不及执行 - 把加锁/解锁/关闭放进
try ... finally块是最小成本的兜底:try { if (!flock($fp, LOCK_EX | LOCK_NB)) { throw new RuntimeException('Lock failed'); } // do work } finally { flock($fp, LOCK_UN); fclose($fp); } - 别依赖
fclose()隐式解锁——句柄可能被复用,或在 Web SAPI 下因进程复用导致锁状态残留
'a' 模式本身具备内核级原子追加,多数情况根本不需要 flock();而 JSON 配置更新这种“读-改-写”三步操作,不加锁就是必然丢数据。锁不是银弹,是为特定竞态条件准备的手术刀。



















