PHP脚本单实例运行:禁止多实例,新实例需立即终止
解决PHP脚本单实例运行:让新请求立即终止,拒绝排队
兄弟,我太懂你这个痛点了!之前用那种创建文件标记的方法踩了无数坑,尤其是浏览器开俩标签同时刷的时候,第二个请求根本不是直接退出,而是乖乖等着第一个跑完,完全达不到要求。其实问题出在两个地方:一是普通文件检查有竞态条件,俩请求同时检查文件都不存在,然后都创建了标记;二是同一浏览器会话的请求会被PHP的会话文件锁卡住,串行执行,第二个请求连检查逻辑都没机会跑。
下面给你两个经过实际验证的靠谱方案:
方案一:非阻塞文件锁 + 提前释放会话锁(首选)
这个方案从根源解决了竞态和排队问题,是最稳定的:
// 第一步:立刻释放会话锁!这是解决浏览器多标签排队的关键 if (session_id()) { session_write_close(); } // 锁文件建议放在web根目录以外的地方,避免被篡改 $lockFilePath = '/tmp/your_script_exclusive_lock'; // 打开锁文件,不存在则自动创建 $lockFileHandle = fopen($lockFilePath, 'w'); if (!$lockFileHandle) { die('无法初始化锁文件,请求终止'); } // 尝试获取**排他锁+非阻塞模式**: // LOCK_EX 确保只有一个实例能拿到锁,LOCK_NB 让拿不到锁的请求直接退出,不等待 if (!flock($lockFileHandle, LOCK_EX | LOCK_NB)) { fclose($lockFileHandle); die('已有脚本实例在运行,当前请求终止'); } // 可选:把当前进程ID写入锁文件,方便后续排查问题 fwrite($lockFileHandle, getmypid()); fflush($lockFileHandle); // -------------------------- // 这里写你的脚本核心业务逻辑 // -------------------------- // 脚本结束时自动释放锁(关闭文件句柄就会自动解锁,不用手动删锁文件) fclose($lockFileHandle);
关键说明:
session_write_close():必须放在最最开头!同一浏览器的会话请求默认会被PHP的会话锁串行处理,释放会话锁后,两个请求才能并行执行,第二个请求才有机会触发锁检查并退出。- 非阻塞锁:
LOCK_NB参数是核心,它让拿不到锁的请求直接返回失败,而不是傻等锁释放。 - 锁文件无需手动删除:进程结束后文件句柄关闭,flock会自动释放锁,就算脚本意外崩溃也不会残留死锁。
方案二:PID文件+进程存活检查(适配特殊环境)
如果你的主机不支持flock(比如某些Windows环境),可以用PID文件配合进程检查的方式:
session_write_close(); // 同样先释放会话锁 $pidFilePath = '/tmp/your_script_pid'; if (file_exists($pidFilePath)) { $existingPid = trim(file_get_contents($pidFilePath)); $isProcessAlive = false; // 检查进程是否真的存活(避免脚本意外崩溃残留PID文件) if (function_exists('posix_kill')) { // 用posix扩展检查:发送0号信号仅验证进程是否存在,不影响进程运行 $isProcessAlive = posix_kill($existingPid, 0); } else { // 没有posix扩展,用系统命令检查(Linux/macOS适用) exec("ps -p $existingPid", $psOutput); $isProcessAlive = !empty($psOutput); // Windows环境请替换为:exec("tasklist /FI \"PID eq $existingPid\"", $psOutput); } if ($isProcessAlive) { die('已有脚本实例在运行,当前请求终止'); } // 如果进程已死,删除无效的PID文件 unlink($pidFilePath); } // 写入当前进程的PID file_put_contents($pidFilePath, getmypid()); // -------------------------- // 你的核心业务逻辑 // -------------------------- // 脚本正常结束时删除PID文件 unlink($pidFilePath);
注意事项:
- 这个方案存在极小的竞态概率,但配合
session_write_close()后几乎可以忽略。 - Windows环境需要替换进程检查的命令,确保能正确识别PID是否存活。
最后再啰嗦两句:
- 锁文件/PID文件一定要放在Web根目录之外,防止被恶意访问或修改。
- 测试的时候可以用不同浏览器同时请求,或者用curl命令并行测试,确保效果符合预期。
内容的提问来源于stack exchange,提问作者Curtis
相关产品推荐
相关产品推荐

