Асинхронная оболочка exec в PHP
У меня есть сценарий PHP, который должен вызывать сценарий оболочки, но не заботится о выводе. Сценарий оболочки выполняет несколько вызовов SOAP и выполняется медленно, поэтому я не хочу замедлять запрос PHP, пока он ожидает ответа. Фактически, PHP-запрос должен иметь возможность завершиться без завершения процесса оболочки.
Я смотрел в различные exec()
, shell_exec()
, pcntl_fork()
и т. д. функции, но ни один из них не предлагает именно то, что я хочу. (Или, если они это сделают, мне не ясно, как.) Есть предложения?
14 ответов
Если он "не заботится о выводе", нельзя ли вызвать exec для скрипта с помощью &
на задний план процесса?
РЕДАКТИРОВАТЬ - с учетом того, что @ AdamTheHutt прокомментировал к этому сообщению, вы можете добавить это к звонку exec
:
" > /dev/null 2>/dev/null &"
Это перенаправит оба stdio
(первый >
) а также stderr
(2>
) чтобы /dev/null
и беги в фоновом режиме.
Есть и другие способы сделать то же самое, но это проще всего прочитать.
Альтернатива вышеупомянутому двойному перенаправлению:
" &> /dev/null &"
Я использовал для этого, поскольку это действительно начало независимого процесса.
<?php
`echo "the command"|at now`;
?>
Для всех пользователей 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
На Linux вы можете сделать следующее:
$cmd = 'nohup nice -n 10 php -f php/file.php > log/file.log & printf "%u" $!';
$pid = shell_exec($cmd);
Это выполнит команду в командной строке, а затем просто вернет PID, который можно проверить на> 0, чтобы убедиться, что он работает.
Этот вопрос похож: есть ли у PHP многопоточность?
/questions/46878073/php-vyipolnit-fonovyij-protsess есть несколько хороших предложений. Я думаю, что мой довольно хорошо, но я предвзятый:)
В Linux вы можете запустить процесс в новом независимом потоке, добавив амперсанд в конце команды
mycommand -someparam somevalue &
В Windows вы можете использовать команду "start" DOS
start mycommand -someparam somevalue
Я использовал это...
/**
* 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.
Правильный способ (!) сделать это
- вилка ()
- setsid ()
- execve ()
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();
Я также нашел Symfony Process Component полезным для этого.
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.
Единственный способ, который я нашел, который действительно работал для меня, был:
shell_exec('./myscript.php | at now & disown')
Вы также можете запустить скрипт PHP как демон или cronjob: #!/usr/bin/php -q
Без очереди использования, вы можете использовать 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 stackru.com';
$process = proc_open($command, $descriptorspec, $pipes);
Используйте названный fifo.
#!/bin/sh
mkfifo trigger
while true; do
read < trigger
long_running_task
done
Затем, когда вы захотите запустить долгосрочную задачу, просто напишите новую строку (неблокирующую в файле триггера).
Пока ваш вклад меньше PIPE_BUF
и это один write()
операции, вы можете записать аргументы в fifo и сделать так, чтобы они отображались как $REPLY
в сценарии.
я не могу использовать > /dev/null 2>/dev/null &
в Windows, поэтому я используюproc_open
вместо. Я запускаю PHP 7.4.23 в Windows 11.
Это мой код.
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());
Все файлы выполняются асинхронно.