PHP中的异步shell执行器

我有一个PHP脚本,需要调用shell脚本,但根本不关心输出。shell脚本执行了许多SOAP调用,完成起来很慢,因此我不想在PHP请求等待应答时降低它的速度。事实上,PHP请求应该能够在不终止shell进程的情况下退出。

我已经研究了各种exec()shell_exec()pcntl_fork()等函数,但它们似乎都没有提供我想要的东西。(或者,即使他们这样做,我也不清楚是如何做到的。)有什么建议吗?

185445 次浏览

如果它“不关心输出”,脚本的exec是否可以用&来调用以作为进程的后台?

编辑 -结合@AdamTheHut对本文的评论,你可以将它添加到exec的调用中:

" > /dev/null 2>/dev/null &"

这将把stdio(第一个>)和stderr (2>)重定向到/dev/null并在后台运行。

做同样的事情还有其他方法,但这是最简单的。


上述双重定向的替代方法:

" &> /dev/null &"

为此我使用了,因为它实际上是在启动一个独立的进程。

<?php
`echo "the command"|at now`;
?>

在linux上,您可以执行以下操作:

$cmd = 'nohup nice -n 10 php -f php/file.php > log/file.log & printf "%u" $!';
$pid = shell_exec($cmd);

这将在命令提示符处执行命令,然后只返回PID,您可以检查> 0以确保它工作正常。

这个问题类似:PHP有线程吗?

php-execute-a-background-process有一些很好的建议。我觉得我的很好,但我有偏见:)

在Linux中,您可以通过在命令末尾附加&号来在一个新的独立线程中启动一个进程

mycommand -someparam somevalue &

在Windows系统中,可以使用“start”DOS命令

start mycommand -someparam somevalue

你也可以以守护进程的计划: #!/usr/bin/php -q的形式运行PHP脚本

做这件事的正确方法是

  1. fork ()
  2. setsid ()
  3. execve ()

Fork Fork, setsid告诉当前进程成为主进程(没有父进程),execve告诉调用进程被被调用进程取代。这样父母可以在不影响孩子的情况下退出。

 $pid=pcntl_fork();
if($pid==0)
{
posix_setsid();
pcntl_exec($cmd,$args,$_ENV);
// child becomes the standalone detached process
}


// parent's stuff
exit();

使用命名fifo。

#!/bin/sh
mkfifo trigger
while true; do
read < trigger
long_running_task
done

然后,每当您想启动长时间运行的任务时,只需向触发器文件写入一个换行符(非阻塞)。

只要你的输入小于PIPE_BUF,并且它是一个单独的write()操作,你就可以把参数写入fifo,并让它们在脚本中显示为$REPLY

我用这个…

/**
* Asynchronously execute/include a PHP file. Does not record the output of the file anywhere.
* Relies on the PHP_PATH config constant.
*
* @param string $filename  file to execute
* @param string $options   (optional) arguments to pass to file via the command line
*/
function asyncInclude($filename, $options = '') {
exec(PHP_PATH . " -f {$filename} {$options} >> /dev/null &");
}

(其中PHP_PATH是一个const,如define('PHP_PATH', '/opt/bin/php5')或类似定义)

它通过命令行传入参数。要在PHP中读取它们,请参见argv

我发现唯一对我有效的方法是:

shell_exec('./myscript.php | at now & disown')

对所有Windows用户:我发现了一个运行异步PHP脚本的好方法(实际上它几乎适用于所有东西)。

它基于popen()和pclose()命令。并且在Windows和Unix上都能很好地工作。

function execInBackground($cmd) {
if (substr(php_uname(), 0, 7) == "Windows"){
pclose(popen("start /B ". $cmd, "r"));
}
else {
exec($cmd . " > /dev/null &");
}
}

http://php.net/manual/en/function.exec.php#86329的原始代码

没有使用队列,你可以像这样使用proc_open():

    $descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "w")    //here curaengine log all the info into stderror
);
$command = 'ping stackoverflow.com';
$process = proc_open($command, $descriptorspec, $pipes);

我还发现Symfony进程组件在这方面很有用。

use Symfony\Component\Process\Process;


$process = new Process('ls -lsa');
// ... run process in background
$process->start();


// ... do other things


// ... if you need to wait
$process->wait();


// ... do things after the process has finished

GitHub回购中查看它是如何工作的。

我不能在Windows上使用 > /dev/null 2>/dev/null &,所以我使用proc_open代替。我在Windows 11上运行PHP 7.4.23。

这是我的代码。


function run_php_async($value, $is_windows)
{
if($is_windows)
{
$command = 'php -q '.$value." ";
echo 'COMMAND '.$command."\r\n";
proc_open($command, [], $pipe);
}
else
{
$command = 'php -q '.$value." > /dev/null 2>/dev/null &";
echo 'COMMAND '.$command."\r\n";
shell_exec($command);
}
}
$tasks = array();


$tasks[] = 'f1.php';
$tasks[] = 'f2.php';
$tasks[] = 'f3.php';
$tasks[] = 'f4.php';
$tasks[] = 'f5.php';
$tasks[] = 'f6.php';


$is_windows = true;


foreach($tasks as $key=>$value)
{
run_php_async($value, $is_windows);
echo 'STARTED AT '.date('H:i:s')."\r\n";
}


在每个要执行的文件中,我放了这个延迟:

<?php
sleep(mt_rand(1, 10));
file_put_contents(__FILE__.".txt", time());

所有文件都是异步执行的。