
PHP flock() 为什么锁不住并发写入
根本原因不是 flock() 失效,而是它只对「同一个文件描述符」有效,且默认是建议性锁(advisory)。如果多个进程各自 fopen() 再 flock(),彼此不共享 fd,锁就形同虚设。
常见错误现象:flock($fp, LOCK_EX) 返回 true,但两个请求仍同时写进文件,内容错乱或覆盖。
- 必须在
fopen()后立即加锁,且全程复用同一个$fp,不能中途fclose()再重开 - 写完必须显式
flock($fp, LOCK_UN),不能依赖脚本结束自动释放(尤其 CLI 下可能长驻) - Linux 下
flock()不跨 NFS 生效;Windows 上对某些网络路径也无效 - 若用
file_get_contents()+file_put_contents(),它们内部不持锁,完全绕过flock()
替代方案:用 stream_set_blocking() 配合 flock() 避免死等
默认 flock($fp, LOCK_EX) 是阻塞的,一个慢请求卡住,后续全排队。线上服务扛不住这种串行化。
- 先
stream_set_blocking($fp, false),再调flock($fp, LOCK_EX | LOCK_NB) - 返回
false表示锁被占用,可立刻重试或返回 429,别硬等 - 注意:
LOCK_NB必须和LOCK_EX或LOCK_SH按位或,单独用无效 - 超时重试建议最多 2–3 次,每次
usleep(50000)(50ms),避免毛刺放大
真正可靠的文件写入:原子性替换 + rename()
当并发写的是完整文件(如缓存、配置快照),比死磕 flock() 更简单可靠的方式是「写临时文件 + 原子重命名」。
立即学习“PHP免费学习笔记(深入)”;
- 生成唯一临时名:
$tmp = $file . '.tmp.' . getmypid() . '.' . uniqid() -
file_put_contents($tmp, $data)写入后,用rename($tmp, $file) -
rename()在同一文件系统下是原子操作,Linux/macOS/Windows 都保证 - 注意:
$tmp和$file必须在同一挂载点,否则rename()会失败并报EXDEV
多服务器部署时,flock() 完全失效怎么办
flock() 是内核级文件描述符锁,只在单机生效。集群环境下,它对其他机器上的 PHP 进程毫无约束力。
- 别试图用 NFS 共享锁文件——NFS 的
flock()行为不可靠,不同版本差异大 - 改用中心化锁服务:Redis 的
SET key value NX PX 10000最常用,配合 Lua 脚本保证释放安全 - MySQL 也能做,但开销大;ZooKeeper/K8s etcd 更重,小项目没必要
- 如果只是防缓存击穿,用「逻辑锁 + 双检」(double-checked locking)更轻量,比如先查 Redis,空则 setnx 占位,再查 DB 回填
文件锁最易被忽略的点:它不解决「业务语义冲突」,只管 I/O 层面的写入互斥。比如两个请求都成功加锁并读到旧值,各自+1再写回,结果还是脏数据——这得靠数据库乐观锁或应用层序列化来兜底。



















