PHP多线程并发更新文本文件失效问题及解决方案咨询
解决PHP文件并发读写导致found.txt被清空的问题
这个问题我太熟了——文件并发读写冲突确实是PHP处理本地文件时的常见坑,尤其是当多个请求同时操作同一个文件(哪怕是修改不同的条目)时,很容易出现数据覆盖甚至清空的情况。咱们一步步来拆解原因和解决方案:
为什么会出现文件清空的情况?
大概率是你的原有代码没有处理并发锁,并且使用了不安全的文件打开方式。举个例子,如果用file_put_contents($file, $content)直接写入,或者用fopen($file, 'w')打开文件,并发场景下会发生:
- 进程A打开文件,用
w模式直接截断了文件内容; - 同时进程B也打开了同一个文件,同样截断了内容;
- 最后不管哪个进程先写入,都会覆盖掉对方的操作,甚至可能因为文件指针混乱导致最终文件为空。
可靠的解决方案:用文件锁(flock)控制并发
核心思路是给文件加排他锁,同一时间只允许一个进程读写文件,确保操作的原子性。下面是修改后的update.php代码,完全解决并发问题:
<?php $pokemon = $_GET['pokemon'] ?? ''; if (empty($pokemon)) { die('请指定pokemon参数'); } $file = 'found.txt'; // 打开文件:c+模式保留原有内容,不存在则创建,不会直接截断 $handle = fopen($file, 'c+'); if (!$handle) { die('无法打开found.txt文件'); } // 加排他锁,阻塞等待直到获取锁(避免并发冲突) if (flock($handle, LOCK_EX)) { // 将文件指针移到文件开头,读取全部内容 fseek($handle, 0); $fileContent = fread($handle, filesize($file)); // 解析现有数据,为空则初始化空数组 $pokemonData = json_decode($fileContent, true) ?? []; // 更新目标pokemon的数值 $currentValue = isset($pokemonData[$pokemon]) ? $pokemonData[$pokemon] : 0; $pokemonData[$pokemon] = $currentValue + 0.0001; // 将指针移回开头,截断文件(清除原有内容),写入新数据 fseek($handle, 0); ftruncate($handle, 0); fwrite($handle, json_encode($pokemonData, JSON_PRETTY_PRINT)); // 释放锁 flock($handle, LOCK_UN); echo "更新成功:{$pokemon} 当前数值为 {$pokemonData[$pokemon]}"; } else { die('暂时无法获取文件锁,请稍后重试'); } // 必须关闭文件句柄,确保锁释放 fclose($handle); ?>
关键细节解释
c+模式打开文件:这是避免文件被意外清空的核心,它不会像w模式那样打开就截断内容,而是保留原有内容,同时支持读写操作。- 排他锁
LOCK_EX:同一时间只有一个进程能持有这个锁,其他请求会等待直到锁释放,从根本上避免了并发读写的冲突。 - 手动控制文件指针和截断:读取内容后,将指针移回开头,截断文件再写入,确保新内容完全覆盖旧内容,不会出现内容拼接的问题。
额外注意事项
- 非阻塞锁的可选方案:如果不想让请求等待锁,可以用
LOCK_EX | LOCK_NB非阻塞模式,然后添加重试逻辑(比如循环3次尝试获取锁),避免用户直接看到错误。 - 文件格式兼容:如果你的
found.txt不是JSON格式(比如每行是pokemon:数值),只需要调整读取和解析的逻辑,核心的锁机制保持不变。 - 高并发场景建议:如果你的并发请求量很大,长期来看用本地txt文件不是最优解,换成SQLite、MySQL这类数据库会更可靠——数据库本身自带事务和锁机制,能更好地处理并发操作。
内容的提问来源于stack exchange,提问作者Human Khoo
相关产品推荐
相关产品推荐

