flock()不能跨进程锁住nfs文件,因其依赖内核本地锁机制,而nfs协议不保证原子性和状态同步,linux会降级为无互斥的本地模拟锁;实操须用df -t确认是否nfs,是则改用sqlite wal或redis分布式锁。

flock() 为什么不能跨进程锁住 NFS 文件
因为 NFS 协议本身不保证 flock() 的原子性和状态同步,Linux 内核对 NFS 挂载点上的 flock() 调用会直接降级为「本地模拟锁」,多个客户端各自认为自己拿到了锁,实际完全没互斥。哪怕挂载时加了 nolock 或 local_lock=all,也改不了这个根本限制。
实操建议:
- 确认文件是否在 NFS 路径下:用
df -T /path/to/file查看文件系统类型,输出含nfs就得换方案 - 绝对不要依赖
flock()保护 NFS 上的配置文件、日志或计数器文件 - 替代方案优先选数据库(如 SQLite 的 WAL 模式)或 Redis(
SET key value NX EX 30做分布式锁)
flock() 必须配合文件描述符使用,不能只传路径
flock() 锁的是「打开的文件描述符」,不是文件路径。如果先 fopen() 再 flock(),但中间没有保持 fd 活跃,或者 fclose() 后还试图用同一个锁句柄,就会失效甚至报错 Warning: flock(): 2 is not a valid stream resource。
常见错误写法:
$fp = fopen('/tmp/counter.txt', 'c+');
flock($fp, LOCK_EX);
// …… 处理逻辑
fclose($fp); // 锁在这里自动释放
// 下面这行会失败,$fp 已无效
flock($fp, LOCK_UN); // ❌
正确做法:
- 所有读写操作必须在
flock()加锁后、fclose()前完成 - 避免在锁区内做耗时操作(如远程 API 调用),否则阻塞其他进程太久
- 用
stream_set_blocking($fp, false)配合LOCK_NB实现非阻塞尝试,避免死等
LOCK_SH 和 LOCK_EX 在同一进程里能共存,但跨进程不行
一个进程可以同时持有一个文件的共享锁(LOCK_SH)和独占锁(LOCK_EX),因为内核按进程粒度管理锁;但不同进程之间,LOCK_EX 会阻塞所有其他 LOCK_SH 和 LOCK_EX,而多个 LOCK_SH 可以并存——这是实现「读多写少」场景的基础。
典型误用:
- 用
LOCK_SH去写文件:不会报错,但其他进程仍可能并发写入,导致数据损坏 - 读取配置后缓存到内存,却用
LOCK_EX锁整个读取过程:没必要,反而降低并发读性能 - 忘记在写操作前检查是否已获得
LOCK_EX,直接 fwrite() —— 看似成功,实则冲突
PHP-FPM 下 flock() 的生命周期受 worker 进程复用影响
PHP-FPM 的每个 worker 进程会复用,如果在某次请求中打开了文件并加了锁,但没显式 fclose(),这个 fd 可能被下次请求复用,导致锁意外残留或冲突。更隐蔽的是,flock() 是「建议性锁」,不强制阻止未调用 flock() 的进程访问文件。
安全实践:
- 始终在
try ... finally块中确保fclose()执行,哪怕发生异常 - 避免在全局变量或静态属性里长期持有
$fp,worker 复用会导致状态污染 - 关键业务(如订单号生成)别只靠
flock(),加一层数据库唯一约束或 Redis 原子 incr 做双重保障
最常被忽略的一点:锁文件本身也要注意权限和位置。/tmp 下的文件可能被系统清理,而 /var/run 又需要 root 权限创建目录;最好把锁文件放在应用可写、且不受自动清理策略影响的私有目录里,比如 sys_get_temp_dir() . '/myapp/lock'。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











