PHP执行shell脚本时如何获取运行中PID并终止对应进程
问题根源
你现在的取消请求不生效,是三个问题叠加导致的:
- 核心阻塞原因是PHP的Session独占锁机制:处理Run按钮请求的PHP脚本,从调用
session_start()开始就会给Session文件加独占锁,在脚本执行结束/主动释放锁之前,其他所有需要使用同一个Session的请求(包括你发的Cancel Ajax请求)都会被阻塞排队,必须等Run的请求跑完释放锁,Cancel请求才能被处理,这时候进程早就执行完了。 - 后台进程启动方式错误:你当前的
exec写法没有把脚本的标准输出、错误输出完全重定向脱离PHP进程,PHP会一直等待管道输出读取完成才会继续往下执行,根本不会立刻返回PID,也不会执行到后续释放锁的逻辑。 - 取消接口存在逻辑错误:
cancelEx的case分支没有加break,就算请求进来也会走到default分支返回错误;另外用ps命令匹配进程名找PID的方式可靠性极差,很容易误杀其他同名进程。
修复方案
1. 修正Run按钮的进程启动逻辑
启动进程时第一时间拿到PID、写入Session后立刻释放Session锁,同时正确配置后台进程的输出重定向,让PHP不用等脚本执行完就可以返回响应:
if( isset($_POST["runBtn"]) ){ session_start(); // 直接通过脚本路径执行,不要读取脚本内容拼接到命令里,避免特殊字符解析错误 $scriptPath = __DIR__ . '/runScript.sh'; $logPath = __DIR__ . '/runtime/run_exec.log'; // 确保日志目录存在且有写入权限 if(!is_dir(dirname($logPath))) mkdir(dirname($logPath), 0755, true); // 正确后台启动命令:nohup挂起+所有输出重定向到日志文件,执行后立刻返回PID $cmd = sprintf( 'nohup bash %s > %s 2>&1 & echo $!', escapeshellarg($scriptPath), escapeshellarg($logPath) ); exec($cmd, $output); $pid = (int)$output[0]; $_SESSION["runPID"] = $pid; // 立刻写入Session数据并释放锁,不阻塞后续其他请求 session_write_close(); // 如果你用的是PHP-FPM,加这行可以直接把响应返回给前端,彻底不阻塞 if(function_exists('fastcgi_finish_request')) fastcgi_finish_request(); // 这里直接返回启动成功响应即可,不需要等脚本跑完 echo json_encode(['code' => 0, 'msg' => '实验已启动', 'pid' => $pid]); exit; }
2. 修正Cancel按钮的后端接口逻辑
去掉无效的script输出,直接读取Session中存储的PID杀进程,补全缺失的break,同时杀掉整个进程组避免残留子进程:
<?php header('Content-Type: application/json'); $aResult = array(); session_start(); if( !isset($_POST['functionname']) ) { $aResult['error'] = 'No function name!'; } if( !isset($aResult['error']) ) { switch($_POST['functionname']) { case 'cancelEx': $pid = $_SESSION['runPID'] ?? 0; if($pid > 0){ // 注意PID前的负号:杀掉整个进程组,避免脚本启动的子进程残留 shell_exec(sprintf('kill -9 -%d 2>&1', $pid)); unset($_SESSION['runPID']); $aResult['result'] = '实验已成功终止'; }else{ $aResult['error'] = '当前没有运行中的实验'; } break; // 必须加break,否则会走到default分支报错 default: $aResult['error'] = 'Not found function '.$_POST['functionname'].'!'; break; } } session_write_close(); echo json_encode($aResult); ?>
注意事项
- 提前创建日志存储目录,确保PHP运行用户对该目录有读写权限,否则输出重定向失败会导致
exec再次阻塞。 - 不要在返回JSON的接口中输出
<script>等HTML内容,会导致前端JSON解析失败,拿不到接口返回结果。 - 如果你的脚本需要长时间运行,建议修改php.ini中
max_execution_time配置,或者在脚本开头加set_time_limit(0),避免PHP超时主动终止进程。
内容的提问来源于stack exchange,提问作者sysSTD
相关产品推荐
相关产品推荐

