PHP检查进程ID

10

我一直想知道的是,我们有一个函数getmypid()可以返回当前脚本的进程ID。是否有类似于checkifpidexists()的函数可用于php中?我的意思是内置的而不是批处理脚本解决方案。

另外,是否有一种方法可以更改脚本的进程ID?

一些明确的说明:

我想检查一个PID是否存在,以查看脚本是否已经在运行中,这样它就不会再次运行,这就像是一个伪cron作业。

我希望更改PID的原因是我可以将脚本PID设置为非常高的值,例如60000,并硬编码该值,以便该脚本只能在该PID上运行,因此只有一个实例会运行。

编辑 - - - -

为了帮助其他人解决这个问题,我创建了这个类:

class instance {

    private $lock_file = '';
    private $is_running = false;

    public function __construct($id = __FILE__) {

        $id = md5($id);

        $this->lock_file = sys_get_temp_dir() . $id;

        if (file_exists($this->lock_file)) {
            $this->is_running = true;
        } else {
            $file = fopen($this->lock_file, 'w');
            fclose($file);
        }
    }

    public function __destruct() {
        if (file_exists($this->lock_file) && !$this->is_running) {
            unlink($this->lock_file);
        }
    }

    public function is_running() {
        return $this->is_running;
    }

}

你可以像这样使用它:

$instance = new instance('abcd'); // the argument is optional as it defaults to __FILE__

if ($instance->is_running()) {
    echo 'file already running';    
} else {
    echo 'file not running';
}

“因此,我可以将脚本PID设置为非常高的值,例如60000,并硬编码该值。”——假设您可以设置PID是有缺陷的。这种方法行不通。 - Ulrich Eckhardt
6个回答

19
在Linux上,您需要查看/proc。
return file_exists( "/proc/$pid" );

在Windows中,您可以使用shell_exec()函数运行tasklist.exe命令,并查找匹配的进程ID:


$processes = explode( "\n", shell_exec( "tasklist.exe" ));
foreach( $processes as $process )
{
     if( strpos( "Image Name", $process ) === 0
       || strpos( "===", $process ) === 0 )
          continue;
     $matches = false;
     preg_match( "/(.*?)\s+(\d+).*$/", $process, $matches );
     $pid = $matches[ 2 ];
}

我认为你想做的是维护一个PID文件。在我编写的守护进程中,它们会检查配置文件,查找pid文件的实例,从pid文件中获取pid,检查/proc/$pid是否存在,如果不存在,则删除pid文件。

   if( file_exists("/tmp/daemon.pid"))
   {
       $pid = file_get_contents( "/tmp/daemon.pid" );
       if( file_exists( "/proc/$pid" ))
       {
           error_log( "found a running instance, exiting.");
           exit(1);
       }
       else
       {
           error_log( "previous process exited without cleaning pidfile, removing" );
           unlink( "/tmp/daemon.pid" );
       }
   }
   $h = fopen("/tmp/daemon.pid", 'w');
   if( $h ) fwrite( $h, getmypid() );
   fclose( $h );

进程ID由操作系统授予,无法保留进程ID。您需要编写守护进程以尊重pid文件。


pcntl_fork()不会改变当前进程的PID!pcntl_fork()函数创建一个子进程,该子进程与父进程仅在其PID和PPID方面不同。有关fork在您的系统上如何工作的详细信息,请参阅您系统的fork(2)手册页。 - ennuikiller
哎呀,我本希望有更跨操作系统的方法来检查 PID 是否存在 :( - Ozzy
通过我的编辑,我提到我认为他不想要pcntl_fork(),也不会得到pid的选择。 - memnoch_proxy
2
Windows的循环应该是这样的:<? foreach ($processes as $process) { if (preg_match('/^(.*)\s+'.$pid.'/', $process)) { return true; } } ?> - Kouber Saparev

6

更好的实现方法是使用 pid 或锁文件。只需要检查 pid 文件是否存在,必要时创建它,并将运行的 pid 填充到其中。

<?
  class pidfile {
    private $_file;
    private $_running;

    public function __construct($dir, $name) {
      $this->_file = "$dir/$name.pid";

      if (file_exists($this->_file)) {
        $pid = trim(file_get_contents($this->_file));
        if (posix_kill($pid, 0)) {
          $this->_running = true;
        }
      }

      if (! $this->_running) {
        $pid = getmypid();
        file_put_contents($this->_file, $pid);
      }
    }

    public function __destruct() {
      if ((! $this->_running) && file_exists($this->_file)) {
        unlink($this->_file);
      }
    }

    public function is_already_running() {
      return $this->_running;
    }
  }
?>

然后按以下方式使用:

<?
  $pidfile = new pidfile('/tmp', 'myscript');
  if($pidfile->is_already_running()) {
    echo "Already running.\n";
    exit;
  } else {
    echo "Started...\n";
  }
?>

这里没有太多的错误检查,但是快速运行显示这在我的系统上可以工作。


问题在于当进行Apache重置时,__destruct未被触发。 - Elia Weiss

4

我使用以下方法来检查Windows机器上是否存在PID:

function pidExists($pid)
{
    exec('TASKLIST /NH /FO "CSV" /FI "PID eq '.$pid.'"', $outputA );
    $outputB = explode( '","', $outputA[0] );
    return isset($outputB[1])?true:false;
}

请注意,$outputB [0] 包含一个消息,即如果 pid 确实不存在,则无法找到 pid!因此,我使用第二个参数进行验证。 编辑: 为了进一步说明,也可以使用 PowerShell 在 Windows 中动态后台生成脚本,就像这样:
    // this function builds an argument list to parse into the newly spawned script. 
    // which can be accessed through the superglobal  global $argv;
    function buildArgList( array $arguments){
     return ' '. implode(' ', $arguments) .' ';
    }

    $arguments = buildArgList(['argument1','argument2','argument3']);
    $windowstyle = 'normal'; // or you can use hidden to hide the CLI

    pclose(popen("powershell start-process -FilePath '" . PHP_BINARY . "' -ArgumentList '-f\"" . $file . " " . $arguments . "\"' -WindowStyle " . $windowstyle,"r"));   

您可以使用spawn出来的脚本,调用cli_set_process_title函数,将该进程的标题设置为一些唯一的哈希值。
在生成子进程的父进程中,您可以使用以下代码,在任务列表中查找窗口标题为uniquehash的进程。
exec('TASKLIST /NH /FO "CSV" /FI "windowtitle eq ' . escapeshellarg($uniquehash) . '"', $output );

当与数据库结合使用时,您可以构建一个工作管理器,用于不同的php脚本之间的通信。

还需要考虑的一点是,PowerShell 对带有空格的路径处理得不太好,这可能会导致它无法正常工作。 - spoofie

1

不,您无法更改任何进程的pid。它由内核分配,并且是内核数据结构的一部分。


0

正如其他人所说,您无法更改进程ID - 它由操作系统的内核分配和完全管理。此外,您还没有说明这是基于命令行还是基于Web服务器:如果是后者,则您甚至可能无法获取脚本的pid。

getmypid()手册页面包含一些“乐观”锁定的示例。我使用“乐观”一词,因为PHP永远不会接近asp.net Web应用程序,其中您具有真正的线程环境,共享/静态类以及可以使用/滥用的Singleton。基本上,您有以下选项:

  • 在文件系统的某个地方触摸“锁定文件”。然后,您的脚本检查该文件是否存在:如果存在,则终止,否则,请触摸该文件并继续处理
  • 设置基于数据库的标志以表示脚本正在运行。与上述相同,但使用db表/字段来标记脚本正在运行。

这两种方法都依赖于脚本正确终止(最后一步是删除锁定文件/数据库标志)。如果脚本由于任何原因崩溃(或机器本身崩溃),则可能需要手动整理过程以删除标志。对于此问题,没有简单的解决方案,但可以探索一个途径,即考虑对锁进行日期时间戳,并采用“如果早于X,则上次运行必定已崩溃”的任意方法。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接