flock() 在 PHP 7.4 中仍有效但需严格遵循句柄生命周期操作:必须作用于 fopen() 返回的 resource,禁用路径直调;须显式解锁、使用 LOCK_NB 非阻塞、配合 try...finally;NFS/Docker 环境易失效,file_put_contents() 的 LOCK_EX 仅适用于简单场景。

flock() 在 PHP 7.4 中依然有效,但必须严格按句柄生命周期操作——跳过显式解锁、忽略返回值、跨 NFS 使用,三者任一都会让锁失效。
flock() 必须作用于 fopen() 返回的资源句柄
常见错误是直接对路径调用 flock(),比如 flock('/tmp/lock.txt', LOCK_EX),这永远返回 false。它不认路径,只认 fopen() 打开后返回的 resource。
- 打开文件推荐用
'c+'(不存在则创建、可读写、不截断)或'a+'(追加、可读),别用纯'a'——它绕过指针定位,可能写偏 - 只读模式
'r'下调用LOCK_EX会失败;若仅需同步信号(不存数据),用fopen('/tmp/flag.lock', 'c')更轻量 - PHP 7.4 不再隐式释放锁:脚本异常退出、
exit()或未捕获异常时,fclose()可能没机会执行,锁就卡住
非阻塞 + 显式解锁是线上标配
默认 flock($fp, LOCK_EX) 是阻塞的,一个慢请求可能拖垮整个服务。PHP 7.4 环境下必须用 LOCK_NB 控制行为。
- 写法必须是
flock($fp, LOCK_EX | LOCK_NB),失败立即返回false,不能靠循环死等 - 最多重试 3 次,每次
usleep(10000)(10ms)+ 指数退避,避免请求积压 -
flock($fp, LOCK_UN)必须显式调用,且放在try...finally块里——PHP 7.0+ 支持,覆盖return、exit、异常等所有退出路径 - 别依赖
fclose()自动解锁:句柄可能被复用,或提前关闭,锁就漏放
file_put_contents() 的 LOCK_EX 不等于万能
它确实简化流程:file_put_contents('/var/log/app.log', $msg, FILE_APPEND | LOCK_EX) 底层调用 flock(),自动完成开/锁/写/解/关。
立即学习“PHP免费学习笔记(深入)”;
- 适合简单日志追加,但不支持超时、重试、自定义降级逻辑
- 锁失败时直接返回
false,你得自己判断并处理(比如写到 syslog 或丢弃) - 它用的是
'a'模式,虽天然追加,但若并发极高,仍建议换'a+'+ 手动flock()以便控制更细粒度 - 别把它当原子写入保障:如果写入内容极大(如 MB 级 JSON),
file_put_contents()可能分块写,中间仍存在被中断风险
NFS、Docker、PHP-FPM 下 flock() 容易静默失效
PHP 7.4 对 flock() 语义没做变更,但底层依赖文件系统。一旦部署环境不是本地 ext4/xfs,锁就大概率形同虚设。
- 运行
mount | grep nfs查是否挂了 NFS;Docker 中检查存储驱动:docker info | grep "Storage Driver",overlay2在某些内核版本下对flock()支持不完整 - 调试时用
lsof -p $(pgrep php-fpm) | grep your_lock_file看句柄是否真持有,比猜更可靠 - Web 和 CLI 进程互不感知锁?不是 bug,是正常现象——PHP-FPM worker 是独立进程,只要都操作同一路径 + 正确调用
flock(),内核锁机制就能协同 - 真正危险的是“锁生效但没人检查返回值”:
flock()是 advisory lock,所有进程都得主动调、主动判,漏一个就全崩
最常被忽略的点:锁本身不解决原子性,只保护写入动作那一小段。写配置文件要防脏读,得用先写临时文件 + rename() 覆盖;高并发计数器类场景,哪怕锁住了,也要注意 ftruncate() + fwrite() 中间是否被中断——这些细节不会报错,但会让数据悄悄出错。



















