PHP简易计数器异常:数值偶尔重置或倒退的原因排查
文本文件计数器异常问题分析与修复建议
问题场景
通过bash命令初始化计数文件:
echo -n 0 > counterfile.txt
初始PHP计数脚本:
<?php $count = 'counterfile.txt'; file_put_contents($count,1+file_get_contents($count),LOCK_EX); ?>
该脚本多数情况正常,但偶尔出现计数器重置、数值倒退的问题,同时询问另一版代码是否更安全:
<?php $count = 'counterfile.txt'; $n = file_get_contents($count); file_put_contents($count,($n++),LOCK_EX); ?>
异常原因
原脚本的核心问题
LOCK_EX仅在file_put_contents写入时生效,但读取file_get_contents($count)的过程没有加锁:
- 并发请求场景下,多个进程会同时读取到同一个旧计数,各自加1后写入,导致计数丢失(比如两个进程同时读5,都写6,实际应该是7);
- 若读取时文件因意外被截断/为空,
file_get_contents返回空字符串,1+""的结果为1,直接触发计数器重置。
异常和十进制无关,完全是并发场景下的锁机制缺失导致的问题。
替代脚本的问题
$n++是后置递增运算符,会先返回$n的原值,再对$n加1。这意味着写入文件的是读取到的旧值,计数器根本不会增长,完全无效。
正确的实现方案
必须将「读取-递增-写入」整个流程放在独占锁的保护下,确保同一时间只有一个进程能操作计数文件。示例代码:
<?php $countFile = 'counterfile.txt'; // 以读写模式打开文件,不存在则创建 $handle = fopen($countFile, 'c+'); if ($handle) { // 加独占锁,阻塞等待直到获取锁 if (flock($handle, LOCK_EX)) { // 将文件指针移至开头 fseek($handle, 0); // 读取内容并转为整数,空值自动转为0 $currentCount = (int)fread($handle, filesize($countFile)); $currentCount++; // 截断文件并写入新计数 ftruncate($handle, 0); fseek($handle, 0); fwrite($handle, $currentCount); // 释放锁 flock($handle, LOCK_UN); } fclose($handle); } ?>
这个实现的优势:
- 全程独占锁保护,避免并发读写冲突;
- 用
(int)强制转换,确保空文件或非法内容被转为0,防止重置; - 完整的文件操作流程,避免部分写入导致的文件损坏。
内容的提问来源于stack exchange,提问作者Luis A. Florit
相关产品推荐
相关产品推荐

