PHP 文件写入并发乱序,flock() 不是可有可无
多个请求同时写同一个文件时,不加锁就可能出现内容交叉、行被拆开、计数丢失这类问题。
打开文件 → 获取文件锁 → 写入内容 → 刷新缓冲 → 释放锁并关闭
很多项目会把回调日志、简单计数、临时任务状态写到文件。单个请求看起来没问题,一旦并发上来,fwrite() 就可能互相穿插。这个时候不是换个写法拼字符串就能稳,得把文件锁补上。
事故现场:两个回调同时写 callback.log

支付回调是很典型的场景。两个订单几乎同时回来,代码都打开同一个 callback.log,然后各自写入一行。没有锁时,写入顺序并不一定按你想的来,日志可能被拆开拼在一起。
• 日志行偶尔缺字段,不一定是上游没传。
• 计数文件偶尔少加一次,可能是并发覆盖。
立即学习“PHP免费学习笔记(深入)”;
• 文件越小越容易被忽略,但小文件也会有并发问题。
文件写入是共享资源,多个请求碰到一起就要排队。
排查时可以先看写文件入口有没有统一封装。如果每个地方都自己 fopen()、fwrite(),出问题时会很难收口。
锁策略对比:写用 LOCK_EX,读可以用 LOCK_SH

flock() 常用几个模式:写入时用 LOCK_EX 独占锁,读取时可以用 LOCK_SH 共享锁。如果不想等待,可以加 LOCK_NB 做非阻塞尝试。
flock($fp, LOCK_EX); flock($fp, LOCK_SH); flock($fp, LOCK_EX | LOCK_NB);
• 写文件时拿 LOCK_EX,避免多个写入互相穿插。
• 读文件时拿 LOCK_SH,避免读到写到一半的内容。
• LOCK_NB 适合拿不到锁就跳过或重试的任务。
锁不是越重越好,要看读写关系。
如果只是追加普通日志,独占锁通常够用。如果是读写同一个状态文件,读取侧也要配合加共享锁。
安全写入顺序:别忘了 fflush() 和 fclose()

拿到锁只是开始,写入顺序也要规范。写完后可以用 fflush() 把缓冲刷到文件,再释放锁并关闭句柄。资源关闭时锁会释放,但代码里写清楚更容易读。
$fp = fopen($file, 'a'); flock($fp, LOCK_EX); fwrite($fp, $line); fflush($fp); flock($fp, LOCK_UN); fclose($fp);
• 失败分支也要释放资源,别让异常路径漏掉关闭。
• 写入大内容时,锁占用时间要控制。
• 追加日志用 a 模式,覆盖写状态文件再按需求选择模式。
锁住之后要快写快放,不要在锁里做慢操作。
比较稳的做法是先把内容在内存里准备好,拿锁后只做文件写入,不要在锁里面查数据库或请求外部接口。
边界提醒:flock() 适合本地文件,不适合所有并发控制

flock() 是建议性锁,前提是大家都按约定使用它。它很适合本地日志、小文件计数、简单任务锁,但多机器部署、远程文件系统、高并发队列,就不该硬靠它扛。
• 单机文件写入,用 flock() 很直接。
• 多台机器抢同一个任务,优先考虑 Redis 锁或数据库锁。
• 高并发日志更适合写到队列、日志系统或专门存储。
文件锁解决的是本地共享文件,不是分布式协调。
这类问题的核心不是记住一个函数,而是认清共享资源。只要多个请求会同时改同一个东西,就要问一句:谁来保证顺序。



















