
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() 更简单可靠的方式是「写临时文件 + 原子重命名」。
Delphi 7应用编程150例 CHM全书内容下载,全书主要通过150个实例,全面、深入地介绍了用Delphi 7开发应用程序的常用方法和技巧,主要讲解了用Delphi 7进行界面效果处理、图像处理、图形与多媒体开发、系统功能控制、文件处理、网络与数据库开发,以及组件应用等内容。这些实例简单实用、典型性强、功能突出,很多实例使用的技术稍加扩展可以解决同类问题。使用本书最好的方法是通过学习掌握实例中的技术或技巧,然后使用这些技术尝试实现更复杂的功能并应用到更多方面。本书主要针对具有一定Delphi基础知识
立即学习“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再写回,结果还是脏数据——这得靠数据库乐观锁或应用层序列化来兜底。










