PHP выполнить фоновый процесс

Мне нужно выполнить копию каталога при действии пользователя, но каталоги достаточно велики, поэтому я хотел бы иметь возможность выполнять такое действие без уведомления пользователя о времени, которое требуется для завершения копирования.

Любые предложения будут высоко ценится.

22 ответа

Решение

Предполагая, что это работает на машине Linux, я всегда обрабатывал это так:

exec(sprintf("%s > %s 2>&1 & echo $! >> %s", $cmd, $outputfile, $pidfile));

Это запускает команду $cmd, перенаправляет вывод команды на $outputfileи записывает идентификатор процесса в $pidfile,

Это позволяет вам легко отслеживать, что делает процесс и работает ли он до сих пор.

function isRunning($pid){
    try{
        $result = shell_exec(sprintf("ps %d", $pid));
        if( count(preg_split("/\n/", $result)) > 2){
            return true;
        }
    }catch(Exception $e){}

    return false;
}

Напишите процесс как серверный скрипт на любом удобном для вас языке (php/bash/perl/etc) и затем вызовите его из функций управления процессом в вашем php-скрипте.

Функция, вероятно, обнаруживает, используется ли стандартный поток ввода-вывода в качестве выходного потока, и если это так, тогда будет установлено возвращаемое значение.

Proc_Close (Proc_Open ("./command --foo=1 &", Array (), $foo));

Я быстро проверил это из командной строки, используя "sleep 25s" в качестве команды, и это сработало как шарм.

( Ответ найден здесь)

Вы можете попытаться добавить это к вашей команде

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

например.

shell_exec('service named reload >/dev/null 2>/dev/null &');

Я просто хотел бы добавить очень простой пример для тестирования этой функциональности в Windows:

Создайте следующие два файла и сохраните их в веб-каталоге:

foreground.php:

<?php

ini_set("display_errors",1);
error_reporting(E_ALL);

echo "<pre>loading page</pre>";

function run_background_process()
{
    file_put_contents("testprocesses.php","foreground start time = " . time() . "\n");
    echo "<pre>  foreground start time = " . time() . "</pre>";

    // output from the command must be redirected to a file or another output stream 
    // http://ca.php.net/manual/en/function.exec.php

    exec("php background.php > testoutput.php 2>&1 & echo $!", $output);

    echo "<pre>  foreground end time = " . time() . "</pre>";
    file_put_contents("testprocesses.php","foreground end time = " . time() . "\n", FILE_APPEND);
    return $output;
}

echo "<pre>calling run_background_process</pre>";

$output = run_background_process();

echo "<pre>output = "; print_r($output); echo "</pre>";
echo "<pre>end of page</pre>";
?>

background.php:

<?
file_put_contents("testprocesses.php","background start time = " . time() . "\n", FILE_APPEND);
sleep(10);
file_put_contents("testprocesses.php","background end time = " . time() . "\n", FILE_APPEND);
?>

Дайте IUSR разрешение на запись в каталог, в котором вы создали вышеуказанные файлы

Разрешите IUSR ПРОЧИТАТЬ и ВЫПОЛНИТЬ C:\Windows\System32\cmd.exe

Хит foreground.php из веб-браузера

Следующее должно быть отображено в браузере с текущими временными метками и локальным ресурсом # в выходном массиве:

loading page
calling run_background_process
  foreground start time = 1266003600
  foreground end time = 1266003600
output = Array
(
    [0] => 15010
)
end of page

Вы должны увидеть testoutput.php в том же каталоге, в котором были сохранены вышеуказанные файлы, и он должен быть пустым

Вы должны увидеть testprocesses.php в том же каталоге, в котором были сохранены вышеуказанные файлы, и он должен содержать следующий текст с текущими временными метками:

foreground start time = 1266003600
foreground end time = 1266003600
background start time = 1266003600
background end time = 1266003610

Если вам нужно просто что-то сделать в фоновом режиме, пока страница PHP не ожидает его завершения, вы можете использовать другой (фоновый) скрипт PHP, который "вызывается" с помощью команды wget. Этот фоновый PHP-скрипт будет выполняться с привилегиями, как и любой другой PHP-скрипт в вашей системе.

Вот пример для Windows, использующий wget из пакетов gnuwin32.

Фоновый код (файл test-proc-bg.php) в качестве примера...

sleep(5);   // some delay
file_put_contents('test.txt', date('Y-m-d/H:i:s.u')); // writes time in a file

Скрипт переднего плана, вызывающий...

$proc_command = "wget.exe http://localhost/test-proc-bg.php -q -O - -b";
$proc = popen($proc_command, "r");
pclose($proc);

Вы должны использовать popen/pclose для правильной работы.

Варианты wget:

-q    keeps wget quiet.
-O -  outputs to stdout.
-b    works on background

Ну, я нашел немного быстрее и проще в использовании версию

shell_exec('screen -dmS $name_of_screen $command'); 

и это работает.

Вот функция для запуска фонового процесса в PHP. Наконец-то создали тот, который на самом деле работает и в Windows, после большого чтения и тестирования различных подходов и параметров.

function LaunchBackgroundProcess($command){
  // Run command Asynchroniously (in a separate thread)
  if(PHP_OS=='WINNT' || PHP_OS=='WIN32' || PHP_OS=='Windows'){
    // Windows
    $command = 'start "" '. $command;
  } else {
    // Linux/UNIX
    $command = $command .' /dev/null &';
  }
  $handle = popen($command, 'r');
  if($handle!==false){
    pclose($handle);
    return true;
  } else {
    return false;
  }
}

Примечание 1: на окнах не используйте /B параметр, как предлагается в другом месте. Это заставляет процесс запускать то же самое окно консоли, что и start сама команда, в результате чего процесс обрабатывается синхронно. Чтобы запустить процесс в отдельном потоке (асинхронно), не используйте /B,

Примечание 2: пустые двойные кавычки после start "" требуются, если команда является путем в кавычках. start Команда интерпретирует первый цитируемый параметр как заголовок окна.

Используйте эту функцию для запуска вашей программы в фоновом режиме. Он кроссплатформенный и полностью настраиваемый.

<?php
function startBackgroundProcess(
    $command,
    $stdin = null,
    $redirectStdout = null,
    $redirectStderr = null,
    $cwd = null,
    $env = null,
    $other_options = null
) {
    $descriptorspec = array(
        1 => is_string($redirectStdout) ? array('file', $redirectStdout, 'w') : array('pipe', 'w'),
        2 => is_string($redirectStderr) ? array('file', $redirectStderr, 'w') : array('pipe', 'w'),
    );
    if (is_string($stdin)) {
        $descriptorspec[0] = array('pipe', 'r');
    }
    $proc = proc_open($command, $descriptorspec, $pipes, $cwd, $env, $other_options);
    if (!is_resource($proc)) {
        throw new \Exception("Failed to start background process by command: $command");
    }
    if (is_string($stdin)) {
        fwrite($pipes[0], $stdin);
        fclose($pipes[0]);
    }
    if (!is_string($redirectStdout)) {
        fclose($pipes[1]);
    }
    if (!is_string($redirectStderr)) {
        fclose($pipes[2]);
    }
    return $proc;
}

Обратите внимание, что после запуска команды по умолчанию эта функция закрывает stdin и stdout запущенного процесса. Вы можете перенаправить вывод процесса в некоторый файл с помощью аргументов $redirectStdout и $redirectStderr.

Примечание для пользователей Windows: нет способа перенаправить stdout/stderr в nul устройство. К сожалению, вы не можете сделать это:

startBackgroundProcess('ping yandex.com', null, 'nul', 'nul');

Поэтому вы должны перенаправить stderr/stdin в допустимый файл или ваша команда должна ожидать, что нет дескриптора stderr/stdout.

Примечания для пользователей *nix:

1) Используйте команду оболочки exec, если вы хотите получить фактический PID:

$proc = startBackgroundProcess('exec ping yandex.com -c 15', null, '/dev/null', '/dev/null');
print_r(proc_get_status($proc));

2) Используйте аргумент $stdin, если вы хотите передать некоторые данные на вход вашей программы:

startBackgroundProcess('cat > input.txt', "Hello world!\n");

Можете ли вы организовать отдельный процесс, а затем запустить свою копию в фоновом режиме? Прошло много времени с тех пор, как я написал PHP, но функция pcntl-fork выглядит многообещающе.

Вы можете попробовать систему очередей, как Resque. Затем вы можете сгенерировать задание, которое обрабатывает информацию и довольно быстро возвращается с "обработкой" изображения. При таком подходе вы не будете знать, когда он закончится.

Это решение предназначено для более масштабных приложений, когда вы не хотите, чтобы ваши передние машины выполняли тяжелую работу, чтобы они могли обрабатывать запросы пользователей. Поэтому он может работать или не работать с физическими данными, такими как файлы и папки, но для обработки более сложной логики или других асинхронных задач (например, новых почтовых сообщений регистрации) его приятно иметь и очень масштабируемый.

Благодаря этому ответу: идеальным инструментом для запуска фонового процесса был бы Symfony Process Component, основанный на proc_* функции, но это гораздо проще в использовании. Смотрите документацию для получения дополнительной информации.

Рабочее решение для Windows и Linux. Узнайте больше на странице My github.

function run_process($cmd,$outputFile = '/dev/null', $append = false){
                    $pid=0;
                if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                        $cmd = 'wmic process call create "'.$cmd.'" | find "ProcessId"';
                        $handle = popen("start /B ". $cmd, "r");
                        $read = fread($handle, 200); //Read the output 
                        $pid=substr($read,strpos($read,'=')+1);
                        $pid=substr($pid,0,strpos($pid,';') );
                        $pid = (int)$pid;
                        pclose($handle); //Close
                }else{
                    $pid = (int)shell_exec(sprintf('%s %s %s 2>&1 & echo $!', $cmd, ($append) ? '>>' : '>', $outputFile));
                }
                    return $pid;
            }
            function is_process_running($pid){
                if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                        //tasklist /FI "PID eq 6480"
                    $result = shell_exec('tasklist /FI "PID eq '.$pid.'"' );
                    if (count(preg_split("/\n/", $result)) > 0 && !preg_match('/No tasks/', $result)) {
                        return true;
                    }
                }else{
                    $result = shell_exec(sprintf('ps %d 2>&1', $pid));
                    if (count(preg_split("/\n/", $result)) > 2 && !preg_match('/ERROR: Process ID out of range/', $result)) {
                        return true;
                    }
                }
                return false;
            }
            function stop_process($pid){
                    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {//'This is a server using Windows!';
                            $result = shell_exec('taskkill /PID '.$pid );
                        if (count(preg_split("/\n/", $result)) > 0 && !preg_match('/No tasks/', $result)) {
                            return true;
                        }
                    }else{
                            $result = shell_exec(sprintf('kill %d 2>&1', $pid));
                        if (!preg_match('/No such process/', $result)) {
                            return true;
                        }
                    }
            }

Если вы хотите выполнить фоновый процесс через PHP, направьте вывод команды в /dev/null и добавить & до конца команды.

exec("bg_process > /dev/null &");

Обратите внимание, что вы не можете использовать $output параметр exec() иначе PHP зависнет (возможно, до завершения процесса).

Я сильно использую fast_cgi_finish_request()

В сочетании с закрытием и register_shutdown_function()

$message ='job executed';
$backgroundJob = function() use ($message) {
     //do some work here
    echo $message;
}

Затем зарегистрируйте это закрытие, которое будет выполнено перед выключением.

register_shutdown_function($backgroundJob);

Наконец, когда ответ был отправлен клиенту, вы можете закрыть соединение с клиентом и продолжить работу с процессом PHP:

fast_cgi_finish_request();

Закрытие будет выполнено после fast_cgi_finish_request.

Сообщение $ не будет видно в любое время. И вы можете зарегистрировать столько замыканий, сколько захотите, но позаботьтесь о времени выполнения скрипта. Это будет работать только в том случае, если PHP работает как модуль Fast CGI (это так?!)

Если вы используете PHP, есть гораздо более простой способ сделать это с помощью pcntl_fork:

http://www.php.net/manual/en/function.pcntl-fork.php

Из официальной документации PHP (php.net)

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

Вместо того, чтобы инициировать фоновый процесс, как насчет создания файла триггера и того, чтобы планировщик, такой как cron или autosys, периодически выполнял скрипт, который ищет и воздействует на файлы триггера? Триггеры могут содержать инструкции или даже необработанные команды (еще лучше, просто сделайте это сценарием оболочки).

Новый ответ на старый вопрос. Используя эту библиотеку , следующий код порождает асинхронный/параллельный PHPThread для выполнения фоновой работы.

  • Должен иметь расширения pcntl, posix и socket
  • Разработано/проверено в режиме командной строки.

Пример кода EZ:

       function threadproc($thread, $param) {
 
    echo "\tI'm a PHPThread.  In this example, I was given only one parameter: \"". print_r($param, true) ."\" to work with, but I can accept as many as you'd like!\n";
 
    for ($i = 0; $i < 10; $i++) {
        usleep(1000000);
        echo "\tPHPThread working, very busy...\n";
    }
 
    return "I'm a return value!";
}
 

$thread_id = phpthread_create($thread, array(), "threadproc", null, array("123456"));
 
echo "I'm the main thread doing very important work!\n";
 
for ($n = 0; $n < 5; $n++) {
    usleep(1000000);
    echo "Main thread...working!\n";
}
 
echo "\nMain thread done working.  Waiting on our PHPThread...\n";
 
phpthread_join($thread_id, $retval);
 
echo "\n\nOur PHPThread returned: " . print_r($retval, true) . "!\n";

Ни один из предыдущих ответов не работает в контексте вызова AJAX или, в частности, AJAX для функции в контроллере.php (например, contollerClass.php/ajaxfunction).

Они подходят только в том случае, если вы хотите запустить 1 функцию на php.page или запустить команду Linux — они не помогут функциям php с аргументами.

Единственный метод, который сработал для меня (после исследования exec/shell_exec/spatie/amphp/gearman/etc..):

завершение сеанса на потом, непосредственно перед фоновой задачей

      function someFn(){
   session_write_close();

 /* code bits in here */

и перезапуск сеанса непосредственно перед завершением фоновой задачи

      
   session_start();
   return / echo json_encode($response)

} // end of function

Сценарии PHP не похожи на другие языки разработки приложений для настольных компьютеров. В языках настольных приложений мы можем настроить потоки демонов для запуска фонового процесса, но в PHP процесс происходит, когда пользователь запрашивает страницу. Однако можно установить фоновое задание, используя функции сервера cron, которые запускает скрипт php.

Для тех из нас, кто использует Windows, посмотрите на это:

Ссылка: http://php.net/manual/en/function.exec.php

Я тоже боролся с тем, чтобы заставить программу работать в фоновом режиме в Windows, пока скрипт продолжает выполняться. Этот метод, в отличие от других решений, позволяет запускать любую программу в свернутом, развернутом виде или без окна вообще. Решение llbra@phpbrasil работает, но иногда оно создает нежелательное окно на рабочем столе, когда вы действительно хотите, чтобы задача выполнялась скрытой.

запуск Notepad.exe сворачивается в фоновом режиме:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("notepad.exe", 7, false); 
?> 

запустить команду оболочки, невидимую в фоновом режиме:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("cmd /C dir /S %windir%", 0, false); 
?> 

Запустите MSPaint в развернутом виде и подождите, пока вы закроете его, прежде чем продолжить скрипт:

<?php 
$WshShell = new COM("WScript.Shell"); 
$oExec = $WshShell->Run("mspaint.exe", 3, true); 
?> 

Для получения дополнительной информации о методе Run() перейдите по адресу: http://msdn.microsoft.com/library/en-us/script56/html/wsMthRun.asp

Отредактированный URL:

Вместо этого перейдите по https://technet.microsoft.com/en-us/library/ee156605.aspx поскольку вышеуказанная ссылка больше не существует.

Я знаю, что это 100-летний пост, но все равно подумал, что это может быть кому-то полезно. Вы можете поместить невидимое изображение где-нибудь на странице, указывая на URL, который должен работать в фоновом режиме, например так:

<img src="run-in-background.php" border="0" alt="" width="1" height="1" />

Другие вопросы по тегам