PHP同时执行两个脚本时文件读写竞态问题及解决方案
场景执行结果
默认PHP生产环境(PHP-FPM、mod_php等多进程/多线程部署模式)下,number.txt的最终值是8。
PHP默认不会对独立请求做串行调度,不同请求的执行是并行的,且普通文件读写操作没有内置原子性保护。你描述的时序下会触发典型的竞态条件:
- 用户1请求执行,读取到文件初始值0
- 用户1还未执行写入操作时,用户2请求进入,同样读取到文件初始值0
- 用户1计算得到7,写入文件
- 用户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
相关产品推荐
相关产品推荐

