如何修复fread(): Length参数必须大于0警告及多脚本文件读写问题
问题分析与修复方案
咱们一步步拆解问题根源,再针对性解决这个fread()警告的问题:
问题核心原因
- 写入时序逻辑漏洞:你原代码里先以
w模式打开文件(这一步会直接把文件截断为空),之后才尝试加排他锁。这就出现了一个「时间窗口」——在fopen和flock之间,其他脚本可能刚好读取到被截断的空文件,导致filesize返回0,传给fread就触发了参数无效的警告。 - 读取操作未加锁:读取文件时没有加共享锁(
LOCK_SH),没法保证读取过程中文件不被写入或修改,filesize和fread拿到的内容完全可能不一致。 - 未处理空文件场景:当文件确实为空时,
filesize返回0,直接传给fread必然触发警告。
修复后的完整代码
function test($n){ echo "<h4>$n at ".time()."</h4>"; $filename = "$n.txt"; for ($i = 0; $i<50; $i++ ){ // 读取文件:加共享锁保证多进程读取一致性 $fp_read = fopen($filename, "r"); $read_content = ''; if ($fp_read) { // 加共享锁,允许多个脚本同时读,写入操作会等待锁释放 if (flock($fp_read, LOCK_SH)) { $file_size = filesize($filename); // 处理文件为空的情况,避免fread警告 $read_content = $file_size > 0 ? fread($fp_read, $file_size) : ''; flock($fp_read, LOCK_UN); // 释放共享锁 } else { echo "Couldn't get shared lock for reading!"; } fclose($fp_read); } // 写入文件:调整时序,先加锁再修改内容 $fp_write = fopen($filename, "c"); // "c"模式:创建(如果不存在)但不截断文件 if ($fp_write) { $new_content = $_SERVER['HTTP_USER_AGENT'].' '.time(); // 加排他锁,其他读写操作必须等待 if (flock($fp_write, LOCK_EX)) { ftruncate($fp_write, 0); // 加锁后再截断文件,避免提前暴露空文件 fwrite($fp_write, $new_content); // 注释/取消注释下面一行,验证fflush的作用 // fflush($fp_write); flock($fp_write, LOCK_UN); // 释放排他锁 } else { echo "Couldn't get exclusive lock for writing!"; } fclose($fp_write); } else { echo "Couldn't open file for writing!"; } } }
关键修复点解析
- 读取加共享锁:用
LOCK_SH获取共享锁,确保多个脚本可以同时读取,但写入操作必须等所有读取锁释放后才能执行,彻底避免读取到中途被修改的文件。 - 写入时序调整:改用
"c"模式打开文件(不会自动截断),先加排他锁,再用ftruncate截断文件并写入新内容。这样就消除了fopen("w")直接截断文件带来的风险窗口。 - 空文件处理:在调用
fread前先判断文件大小,为空时直接返回空字符串,从根源上避免无效参数警告。
关于fflush的验证方法
要验证未使用fflush时数据是否真的被写入,可以做以下测试:
- 注释掉
fflush($fp_write)这一行,同时运行多个脚本实例。 - 观察其他脚本读取到的内容——由于PHP的文件缓冲区机制,数据可能暂时存在内存中,未立即写入磁盘,这时候其他脚本可能读取到旧内容。
- 取消注释
fflush后再测试,会发现读取到的内容是实时更新的,因为fflush强制将缓冲区内容写入磁盘。不过要注意,即使不用fflush,当文件句柄关闭或锁释放后,操作系统也会在合适时机刷新缓冲区,只是fflush能强制立即生效。
内容的提问来源于stack exchange,提问作者Johny Bony
相关产品推荐
相关产品推荐

