12

我的意图是这样的。

我的client.html 通过ajax 调用一个php 脚本check.php。我想让 check.php 检查另一个脚本 task.php 是否已经在运行。如果是,我什么都不做。如果不是,我需要在后台运行它。

我有一个想法,我想做什么,但不确定如何去做。

A 部分。我知道如何通过 ajax 调用 check.php。

B 部分。在 check.php 中,我可能需要运行 task.php。我想我需要类似的东西:

$PID = shell_exec("php task.php > /dev/null & echo $!");

我认为 "> /dev/null &" 位告诉它在后台运行,但不确定 "$!" 是什么 做。

C 部分。我需要的 $PID 作为进程的标记。我需要将此数字(或其他)写入同一目录中的文件,并且需要在每次调用 check.php 时读取它。我不知道该怎么做。有人可以给我一个链接,说明如何在同一目录中读取/写入具有单个数字的文件吗?

D部分。然后检查上次启动的task.php是否仍在运行,我将使用该函数:

function is_process_running($PID)
{
   exec("ps $PID", $ProcessState);
   return(count($ProcessState) >= 2);
}

我认为这就是我需要的所有部分,但正如你所看到的,我不确定如何做其中的一些。

4

6 回答 6

15

我会使用一个flock()基于机制来确保task.php它只运行一次。

使用这样的代码:

<?php

$fd = fopen('lock.file', 'w+');

// try to get an exclusive lock. LOCK_NB let the operation not blocking
// if a process instance is already running. In this case, the else 
// block will being entered.
if(flock($fd, LOCK_EX | LOCK_NB )) {
    // run your code
    sleep(10);
    // ...
    flock($fd, LOCK_UN);
} else {
    echo 'already running';
}

fclose($fd);

另请注意flock(),正如 PHP 文档所指出的那样,可在所有支持的操作系统上移植。


!$

为您提供 bash 中最后执行的程序的 pid。像这样:

command &
pid=$!
echo pid

请注意,您必须确保您的 php 代码在支持 bash 的系统上运行。(不是窗户)


更新(在揭幕战评论后)。

flock()将适用于所有操作系统(正如我所提到的)。我在使用 Windows 时在您的代码中看到的问题是!$(正如我提到的;)..

要获取 task.php 的 pid,您应该使用proc_open()启动 task.php。我准备了两个示例脚本:

任务.php

$fd = fopen('lock.file', 'w+');

// try to get an exclusive lock. LOCK_NB let the operation not blocking
// if a process instance is already running. In this case, the else 
// block will being entered.
if(flock($fd, LOCK_EX | LOCK_NB )) {
    // your task's code comes here
    sleep(10);
    // ...
    flock($fd, LOCK_UN);
    echo 'success';
    $exitcode = 0;
} else {
    echo 'already running';
    // return 2 to let check.php know about that
    // task.php is already running
    $exitcode = 2; 
}

fclose($fd);

exit($exitcode);

检查.php

$cmd = 'php task.php';
$descriptorspec = array(
   0 => array('pipe', 'r'),  // STDIN 
   1 => array('pipe', 'w'),  // STDOUT
   2 => array('pipe', 'w')   // STDERR
);

$pipes = array(); // will be set by proc_open()

// start task.php
$process = proc_open($cmd, $descriptorspec, $pipes);

if(!is_resource($process)) {
    die('failed to start task.php');
}

// get output (stdout and stderr)
$output = stream_get_contents($pipes[1]);
$errors = stream_get_contents($pipes[2]);

do {
    // get the pid of the child process and it's exit code
    $status = proc_get_status($process);
} while($status['running'] !== FALSE);

// close the process
proc_close($process);

// get pid and exitcode
$pid = $status['pid'];
$exitcode = $status['exitcode'];

// handle exit code
switch($exitcode) {
    case 0:
        echo 'Task.php has been executed with PID: ' . $pid
           . '. The output was: ' . $output;
        break;
    case 1:
        echo 'Task.php has been executed with errors: ' . $output;
        break;
    case 2:
        echo 'Cannot execute task.php. Another instance is running';
        break;
    default:
        echo 'Unknown error: ' . $stdout;
}

你问我为什么我的 flock()解决方案是最好的。这只是因为另一个答案不能可靠地确保task.php运行一次。这是因为我在该答案下方的评论中提到的竞争条件。

于 2013-03-30T22:21:34.193 回答
5

您可以使用锁定文件来实现它:

if(is_file(__DIR__.'/work.lock'))
{
    die('Script already run.');
}
else
{
    file_put_contents(__DIR__.'/work.lock', '');
    // YOUR CODE
    unlink(__DIR__.'/work.lock');
}
于 2013-03-30T22:22:38.723 回答
2

可惜在被接受之前我没有看到这个..

我写了一个类来做到这一点。(使用文件锁定)和 PID,进程 ID 检查,在 Windows 和 Linux 上。

https://github.com/ArtisticPhoenix/MISC/blob/master/ProcLock.php

于 2017-07-16T17:21:00.517 回答
1

我认为您在所有流程和背景调查方面确实做得过火了。如果您运行PHP脚本without a session,那么您实际上已经在后台运行它。因为它不会阻止来自用户的任何其他请求。所以确保你不打电话session_start();

然后下一步就是在用户取消请求时运行它,这是PHP. 忽略用户中止

最后的检查是确保它只运行一次,这可以通过创建文件轻松完成,因为PHP没有简单的应用程序范围。

结合:

<?php
// Ignore user aborts and allow the script
// to run forever
ignore_user_abort(true);
set_time_limit(0);

$checkfile = "./runningtask.tmp";

//LOCK_EX basicaly uses flock() to prevents racecondition in regards to a regular file open.
if(file_put_contents($checkfile, "running", LOCK_EX)===false) {
    exit();
}

function Cleanup() {
  global $checkfile;
  unlink($checkfile);
}


/*
actual code for task.php    
*/


//run cleanup when your done, make sure you also call it if you exit the code anywhere else
Cleanup();
?>

在您的 javascript 中,您现在可以直接调用 task.php 并在与服务器建立连接后取消请求。

<script>
function Request(url){
  if (window.XMLHttpRequest) { // Mozilla, Safari, ...
      httpRequest = new XMLHttpRequest();
  } else if (window.ActiveXObject) { // IE
      httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
  } else{
      return false;
  }
  httpRequest.onreadystatechange = function(){
      if (httpRequest.readyState == 1) {
        //task started, exit
        httpRequest.abort();
      }
  };
  httpRequest.open('GET', url, true);
  httpRequest.send(null);
}

//call Request("task.php"); whenever you want.
</script>

加分项:您可以让 task.php 的实际代码编写偶尔的更新以$checkfile了解正在发生的事情。然后你可以让另一个 ajax 文件读取内容并向用户显示状态。

于 2017-07-13T08:59:54.957 回答
1

让从B到D的整个过程变得简单

步骤 BD:

$rslt =array(); // output from first exec
$output = array(); // output of task.php execution

//Check if any process by the name 'task.php' is running
exec("ps -auxf | grep 'task.php' | grep -v 'grep'",$rslt);

if(count($rslt)==0) // if none,
  exec('php task.php',$output); // run the task,

解释:

ps -auxf        --> gets all running processes with details 
grep 'task.php' --> filter the process by 'task.php' keyword
grep -v 'grep'  --> filters the grep process out

注意:

  1. 建议在 task.php 文件中进行相同的检查。

  2. 如果task.php直接通过httpd(webserver)执行,只会显示为httpd进程,不能被'ps'命令识别

  3. 它在负载平衡的环境下不起作用。[编辑:2017 年 7 月 17 日]

于 2017-07-14T01:39:31.427 回答
1

您可以在脚本运行期间获得脚本本身的独占锁定

一旦调用 lock() 函数,任何其他运行它的尝试都将结束。

//try to set a global exclusive lock on the file invoking this function and die if not successful
function lock(){
  $file = isset($_SERVER['SCRIPT_FILENAME'])?
    realpath($_SERVER['SCRIPT_FILENAME']):
    (isset($_SERVER['PHP_SELF'])?realpath($_SERVER['PHP_SELF']):false);
  if($file && file_exists($file)){
    //global handle stays alive for the duration if this script running
    global $$file;
    if(!isset($$file)){$$file = fopen($file,'r');}
    if(!flock($$file, LOCK_EX|LOCK_NB)){
        echo 'This script is already running.'."\n";
        die;
    }
  }
}

测试

在一个 shell 中运行它,并在等待输入时尝试在另一个 shell 中运行它。

lock();

//this will pause execution until an you press enter
echo '...continue? [enter]';
$handle = fopen("php://stdin","r");
$line = fgets($handle);
fclose($handle);
于 2017-07-17T16:21:05.463 回答