PHP如何实现文件锁定后读取内容再覆盖(截断)写入?
问题根因
w+模式的固有特性导致了这个问题:调用fopen($file, "w+")时,PHP会在打开文件的瞬间直接清空文件全部内容,这个操作发生在你调用flock加锁之前,因此你拿到独占锁后读取到的内容必然是空的。更严重的是多进程场景下,即使进程没有抢到锁,只要执行了fopen("w+")也会直接清空文件,完全不符合你先读取再修改的需求。
你现有代码还存在两处明显错误:
explode的第三个参数错误传入了锁常量LOCK_EX,这会导致分割最多只返回2个元素,不符合队列分割的预期- 没有处理文件指针位置和旧内容残留的问题,直接写入会导致新内容比旧内容短时,文件尾部残留旧数据
修正方案
改用r+模式打开文件,该模式支持读写且不会在打开时清空文件,流程调整为:打开文件→加独占锁→读取内容→处理逻辑→将文件指针移到开头→清空文件内容→写入新内容→解锁→关闭文件。
修正后的代码如下:
$fp = fopen($file, "r+"); if(flock($fp, LOCK_EX)) { // 用fstat获取文件真实大小,避免filesize缓存问题 $fstat = fstat($fp); $filesize = $fstat['size']; $content = fread($fp, $filesize); echo $content; // 修正explode参数,按换行分割队列 $job_queue = explode("\n", $content); $next_job = array_shift($job_queue); // 指针移到文件开头 rewind($fp); // 清空文件原有内容 ftruncate($fp, 0); // 写入新的队列内容 fwrite($fp, implode("\n", $job_queue)); flock($fp, LOCK_UN); } else { echo '<br>Error: cannot lock job queue file'; } fclose($fp);
补充注意事项
- 不要在加锁前读取文件内容,否则读取到的数据可能已经被其他进程修改,出现竞态问题
- 如果队列内容可能包含空行,建议使用更可靠的序列化方式存储队列数据,避免换行分割出现异常
- 高并发场景下可以给
flock加LOCK_NB参数实现非阻塞锁,避免进程长时间挂起等待
内容的提问来源于stack exchange,提问作者jack
相关产品推荐
相关产品推荐

