PHP中flock()是最可靠文件锁方案,必须作用于fopen()打开的文件句柄,推荐LOCK_EX|LOCK_NB非阻塞加锁,配合try/finally确保显式解锁,避免因异常、fastcgi_finish_request或pcntl_fork导致锁泄漏。

PHP 中用 flock() 加锁最可靠
直接用 flock(),别碰 sem_acquire() 或自建文件标记——前者依赖系统信号量且 Windows 支持差,后者在并发高时大概率漏锁或死锁。flock() 是内核级的 advisory lock,PHP 原生封装得稳,只要文件句柄没关、进程没退出,锁就一直有效。
关键点:flock() 锁的是文件指针(resource),不是文件路径;必须配合 fopen() 使用,且不能用 file_get_contents() 这类封装函数替代。
- 加锁前务必检查
fopen()是否成功,否则flock()会警告并返回false - 推荐用
LOCK_EX | LOCK_NB组合:阻塞式锁易卡住整个请求,非阻塞+手动重试更可控 - 锁住后记得
fwrite()或fseek()等操作,否则某些 NFS 环境下锁可能被提前释放
非阻塞加锁失败时怎么处理
直接 flock($fp, LOCK_EX) 在繁忙场景下可能无限等待,导致 PHP 超时或 Apache 进程堆积。用 LOCK_NB 主动控制逻辑分支更安全。
if (!flock($fp, LOCK_EX | LOCK_NB)) {
// 加锁失败:可记录日志、sleep 后重试、或直接返回错误
error_log("Lock failed on " . basename($lock_file));
fclose($fp);
return false;
}
注意:flock() 的非阻塞模式在 Windows 上行为不一致(部分版本忽略 LOCK_NB),生产环境务必在目标系统实测;若需跨平台强一致性,应改用数据库行锁或 Redis 分布式锁。
flock() 释放时机和常见泄漏点
锁在以下任一情况自动释放:文件指针被 fclose() 关闭、脚本执行结束、进程退出。但有三个典型泄漏场景:
- 异常未捕获导致
fclose()没执行——必须用try/finally包裹锁操作 - 脚本被
fastcgi_finish_request()提前结束,但进程仍在运行,锁持续存在 - 使用
pcntl_fork()后子进程继承了文件描述符,父子进程共用一把锁,容易误释放
示例安全写法:
$fp = fopen($lock_file, 'c');
if (!$fp) die('Cannot open lock file');
try {
if (!flock($fp, LOCK_EX | LOCK_NB)) {
throw new Exception('Cannot acquire lock');
}
// 执行受保护操作,如写配置、更新计数器等
} finally {
flock($fp, LOCK_UN); // 即使异常也确保解锁
fclose($fp);
}
什么情况下 flock() 根本不生效
flock() 是 advisory lock(建议性锁),所有参与者都得主动调用才起作用。它对以下情况完全无效:
- 直接用 shell 命令修改文件(如
echo "x" > config.php)——绕过 PHP 文件句柄,锁形同虚设 - Web 服务器以不同用户身份运行(如 www-data vs root),Linux 下不同 UID 进程间
flock()互不感知 - 挂载的网络文件系统(NFS、CIFS)未启用
local_lock或服务端不支持 byte-range lock
如果业务涉及 CLI 脚本和 Web 请求同时操作同一文件,必须统一入口(比如全走 API),或改用 mkdir() 原子建目录作为锁(仅限 Linux),否则锁机制极易失效。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











