flock() 是 php 中多进程安全写入文件最直接有效的方式,需配合 fopen() 使用,锁绑定于文件指针、非路径,必须检查返回值并在临界区前后正确加锁/解锁。

PHP中用 flock() 对文件加锁最直接有效
多进程同时写入一个文件,不加锁会导致内容错乱、覆盖或截断。flock() 是 PHP 内置的 advisory locking(建议性锁)机制,依赖底层系统支持(Linux/macOS/Windows 均可用),它操作的是文件描述符而非文件路径,所以必须配合 fopen() 使用。
关键点:锁是与打开的文件指针绑定的,不是与文件路径绑定;进程退出或关闭文件句柄时自动释放锁;多个进程对同一文件调用 flock() 会阻塞或失败,取决于是否传 LOCK_NB。
- 必须在
fopen()之后、fwrite()之前调用flock($fp, LOCK_EX) - 写完后必须调用
flock($fp, LOCK_UN)手动释放(或靠fclose()自动释放) - 不要对只读打开的文件句柄加写锁(
LOCK_EX),否则flock()返回false - 加锁失败时应判断并重试或报错,不能直接写入
flock() 阻塞 vs 非阻塞模式怎么选
默认 flock($fp, LOCK_EX) 是阻塞的:如果锁被占用,当前进程会挂起等待。这对日志写入等低频场景够用;但高并发下可能拖慢响应,甚至引发超时或雪崩。
加 LOCK_NB 可转为非阻塞模式,失败立即返回 false,适合需要快速失败或自定义重试逻辑的场景。
立即学习“PHP免费学习笔记(深入)”;
- 阻塞写法:
flock($fp, LOCK_EX)—— 简单但有等待风险 - 非阻塞写法:
if (!flock($fp, LOCK_EX | LOCK_NB)) { /* 处理冲突 */ } - 常见错误:忘记检查
flock()返回值,导致“以为锁住了”却实际没锁上 - 注意:即使用了
LOCK_NB,也不代表能完全避免竞争——它只防止两个进程同时进入临界区,不解决“检查-写入”之间的竞态(即 TOCTOU),所以仍需确保所有写操作都在锁内完成
为什么不能用 file_put_contents() 直接加锁
file_put_contents() 的第四个参数可传 FILE_APPEND | LOCK_EX,看起来方便,但它内部是「打开→加锁→写入→关闭」一气呵成,锁的生命周期极短,仅覆盖写入瞬间。这无法保护更复杂的操作,比如先读取再修改再写回(如计数器累加)。
- 适用场景仅限:纯追加(
FILE_APPEND)或覆盖写入,且不需要读取旧内容 - 不适用场景:读-改-写流程(如
$n = (int)file_get_contents('counter'); file_put_contents('counter', $n+1);),中间存在竞态窗口 - 性能上,每次调用都开闭文件,比复用
fopen()+flock()开销更大 - 错误信息容易被忽略:
file_put_contents()加锁失败时静默失败(返回false),不如显式flock()易于调试
真正安全的多进程写入模板长什么样
安全写入不是“加个锁就完事”,而是要明确临界区边界、处理失败、避免死锁。下面是一个最小可行模板:
$fp = fopen('data.txt', 'c+');
if (!$fp) {
throw new RuntimeException('Cannot open file');
}
if (!flock($fp, LOCK_EX | LOCK_NB)) {
fclose($fp);
throw new RuntimeException('Lock failed, file busy');
}
// 此处是临界区:可安全读、改、写
fseek($fp, 0);
$content = stream_get_contents($fp);
rewind($fp);
fwrite($fp, $content . "new line\n");
fflush($fp); // 确保写入磁盘
flock($fp, LOCK_UN);
fclose($fp);
注意几个易漏细节:'c+' 模式保证文件存在且可读写;fflush() 强制刷盘,避免缓冲区延迟;flock($fp, LOCK_UN) 必须显式调用,不能只靠 fclose() —— 因为 fclose() 在异常提前退出时可能不执行。
真正的难点不在语法,而在于“哪些操作必须放进锁里”。比如生成唯一 ID、校验数据一致性、更新多个关联文件——这些逻辑一旦跨出锁范围,就等于没锁。











