You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP同时执行两个脚本时文件读写竞态问题及解决方案

场景执行结果

默认PHP生产环境(PHP-FPM、mod_php等多进程/多线程部署模式)下,number.txt的最终值是8。

PHP默认不会对独立请求做串行调度,不同请求的执行是并行的,且普通文件读写操作没有内置原子性保护。你描述的时序下会触发典型的竞态条件:

  1. 用户1请求执行,读取到文件初始值0
  2. 用户1还未执行写入操作时,用户2请求进入,同样读取到文件初始值0
  3. 用户1计算得到7,写入文件
  4. 用户2计算得到8,写入文件,覆盖用户1写入的7

最终文件保存的是后写入的8,只有单进程调试模式这类极端场景下才会出现串行执行得到15的结果,生产环境不会默认采用这种部署方式。

实现正确累加的方案

要得到正确的累加结果15,核心是消除读写间隙的竞态条件,常见方案有三类:

  • 采用文件排他锁:不要使用分离的读文件、写文件函数,在同一个文件句柄上先获取排他锁,持有锁完成读、改、写全流程后再释放锁,保证同一时间只有一个请求能操作该文件。参考实现:
// 打开计数文件,c+模式不会清空原有内容,文件不存在时自动创建
$fileHandle = fopen('number.txt', 'c+');
// 申请排他锁,锁被占用时会阻塞等待直到获取成功
if (flock($fileHandle, LOCK_EX)) {
    // 读取当前存的数值
    $num = (int)stream_get_contents($fileHandle);
    // 累加提交值
    $num += (int)$_POST['add'];
    // 清空原有文件内容,重置指针到开头
    ftruncate($fileHandle, 0);
    rewind($fileHandle);
    // 写入新值
    fwrite($fileHandle, (string)$num);
    // 释放排他锁
    flock($fileHandle, LOCK_UN);
}
fclose($fileHandle);
  • 使用支持原子操作的存储替换普通文件:把计数存在Redis、MySQL这类天生支持原子操作的组件中,不需要手动实现锁逻辑。比如Redis直接调用INCRBY number 传入值命令,MySQL执行UPDATE counter SET count_value = count_value + ? WHERE count_key = 'number'语句,这类单条操作在存储引擎层面是原子执行的,不会出现并发读写冲突。
  • 请求串行化处理:把所有修改计数的请求投递到消息队列,用单独的消费进程按顺序逐个处理所有修改请求,从架构层面避免并发操作同一个计数资源。

内容的提问来源于stack exchange,提问作者Ari Jesus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 09:36:20