Выполнение фонового процесса в PHP
Мне нужно выполнить копирование каталогов после некоторого действия пользователя, но каталоги довольно большие, поэтому я хотел бы иметь возможность выполнить эту операцию, не зная, сколько времени потребуется для завершения копирования. Как решить данную задачу?
Ответ 1
Предполагая, что вы работаете в среде 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;
>
Ответ 2
Ответ 3
Простой пример для этой функции в Windows:
Создайте следующие два файла и сохраните их в веб-каталоге:
foreground.php:
ini_set(«display_errors»,1);
error_reporting(E_ALL);
echo «
загрузка страницы
«;
function run_background_process()
file_put_contents(«testprocesses.php»,»время запуска1 = » . time() . «\n»);
echo »
время запуска1 = ".time()."
«;
// вывод команды должен быть перенаправлен в файл или другой поток вывода
// http://ca.php.net/manual/en/function.exec.php
exec(«php background.php > testoutput.php 2>&1 & echo $!», $output);
echo «
время завершения1 = " . time() . "
«;
file_put_contents(«testprocesses.php»,»время завершения1 = «.time().»\n», FILE_APPEND);
return $output;
>
echo «
вызов фонового процесса
«;
$output = run_background_process();
echo «
output = "; print_r($output); echo "
«;
echo «
end of page
«;
?>
background.php:
file_put_contents(«testprocesses.php»,»время запуска2 = » . time() . «\n», FILE_APPEND);
sleep(10);
file_put_contents(«testprocesses.php»,»время завершения2 = » . time() . «\n», FILE_APPEND);
?>
Дайте IUSR разрешение на запись в каталог, в котором вы создали указанные выше файлы.
Выполните следующую последовательность команд:
loading page
calling run_background_process
время запуска1 = 1266003600
время заврешения1 = 1266003600
output = Array (
[0] => 15010)
end of page
Вы должны получить testoutput.php в том же каталоге, в котором были сохранены указанные выше файлы, и он должен быть пустым.
Вывод будет подобным такому:
Время запуска1 = 1266003600
Время завершения1 = 1266003600
Время запуска2 = 1266003600
Время завершения2 = 1266003610
Ответ 4
Если вам нужно просто сделать что-то в фоновом режиме, не дожидаясь завершения PHP операции, вы можете использовать фоновый PHP-скрипт, который «вызывается» командой wget. Этот фоновый сценарий PHP будет выполняться с привилегиями, конечно же, как и любой другой сценарий PHP в вашей системе.
Вот пример использования wget в Windows из пакетов gnuwin32.
Фоновый код (файл test-proc-bg.php) в качестве примера :
sleep(5); // задержка
file_put_contents(‘test.txt’, date(‘Y-m-d/H:i:s.u’)); // запись в файл
Скрипт, вызывающий фоновый процесс:
$proc_command = «wget.exe http://localhost/test-proc-bg.php -q -O — -b»;
$proc = popen($proc_command, «r»);
pclose($proc);
Необходимо использовать popen/pclose, чтобы это работало правильно.
Параметры wget:
-q — активный режим.
-O — вывод в stdout.
-b — фоновый режим.
Мы будем очень благодарны
если под понравившемся материалом Вы нажмёте одну из кнопок социальных сетей и поделитесь с друзьями.
PHP: запуск задачи в фоне
Какие самые правильные и надежные подходы стоит использовать, если необходимо запустить некую длительную задачу в фоновом режиме?
Приведу пример, чтобы стало понятней:
есть основной php скрипт, контроллер, который обрабатывает пользовательские действия. Пользователь нажимает в интерфейсе волшебную кнопку, которая должна запустить долгоиграющую задачу и вернуть управление пользователю. При этом не требуется возвращать или как то дополнительно обрабатывать результат работы длительного скрипта.
Знаю, что для подобных задач существуют различные библиотеки для создания очереди сообщений, но конкретно для моего примера это избыточно.
Так же хотелось бы обойтись без cron-task-ов и других ОС-специфичных вещей.
Средний 1 комментарий
По-моему, в вашем случае самым простым вариантом будет exec:
exec("php /www/site/script.php >>/www/logs/script.log 2>&1 &");
К сожалению это будет работать только в *nix, потому что в Windows запустить процесс в фоне не так то просто
В *nix тоже не всегда — например при специфических настройках SELinux
А ещё может быть, что в насйтроках php отключен exec (но это встречается довольно редко)
В windows нужно перед началом команды прописать start /b , а также можно указать время выполнения дополнительной командой at в конце вашей команды. Также если смотреть в мануале по PHP про команду exec , то в первом комментарии можно увидеть код(функцию) для разделения команд на unix/windows машинах.
PHP CLI в фоне ведет себя плохо. Из того, что применяется «на скорую руку», хорошо себя ведут BASH, Perl и Python.
В задаче, где надо было беречь ресурсы, поступил так:
1. Скрипт на PHP сохраняет «задание» на BASH в некоторую директорию job
2. Крон ежеминутно запускает некий скрипт dispatcher, написанный на Perl (был жизнеспособный вариант и на BASH) который ищет N самых старых (по last modified time) скриптов из директории job, и запускает их.
3. Запускаемые скрипты первым делом уничтожают (rm) себя, чтобы не Они все равно выполнятся — ведь они уже загружены в ОЗУ. Результаты работы сохраняются в ФС или в БД, для обслуживания логики интерфейса
Число N для каждого конкретного сервера выявляется индивидуально — тестированием.
Был «подводный камень», который по первому разу даже был обнаружен совершенно невовремя — все, что запускает крон, должно работать с абсолютными путями.
Таким образом были сделаны проект вроде ютуба и аналогичный, тоже конвертирующий видео.
А что значит «PHP CLI в фоне ведет себя плохо»? Можете как-то расшифровать эту фразу? В чём конкретно это проявляется?
Если сможете — отследите AVG при задачах, выполнение которых занимает более 30 секунд. Кроме этого, интернет полнится слухами, проверять которые нет желания. Просто последовали по пути наименьшего сопротивления, и стали применять ту технологию, которую не ругают, а напротив — хвалят, в этой области задач.
Приходилось писать фоновые вещи на php. Если правильно подходить к написанию кода, то такой скрипт работает неделями, аки демон. Конечно, лучше было бы написать на чем-нибудь, более приспособленном для таких задач, тут я с вами согласен.
Утилита для запуска PHP-скриптов в фоновом режиме
Когда писал утилиту, рассчитывал на то, что приидется использовать на разных серверах и в разных условиях, в которых не всегда можно воспользоваться консолью (платный / бесплатный хостинг) и в условиях, когда PHP используют как модуль Apache (в таком случае про pcntl_fork() можно забыть).
Принцип действия
Скрипт создается / редактируется в браузере
В случае, если нужно работать с телефона / планшета, или нет FTP-доступа к серверу. Использован замечательный редактор ace (ajax.org).
Запуск скрипта
Для запуска браузер «дергает» за файл, который подключает класс в начало скрипта и пускает все вместе на выполнение. Типичный метод породить новый процес.
Ход работы скрипта
Мы можем видеть все сообщения, которые выводит скрипт, в реальном времени.
Для вывода сообщений в консоль (импровизированную, в окне браузера), скрипт должен вызвать функцию
После этого браузер получает сообщение об изменениях в файле консоли и обновляет консоль в браузере, уже с нашим сообщением (механизм долго объяснять, если кому-то интересно — могу написать по этой теме статью).
Если в скрипте просто указать бесконечный цикл и запустить, то остановить его можно только остановкой процесса (что мы с помощью PHP не сможем сделать). Во избежание такой ситуации, целесообразно периодически использовать функцию
которая возвращает false в случае, когда вы решили остановить выполнение скрипта и нажали соответствующую кнопку у себя в браузере.
Если скрипт рассчитан на «прослушку» чего-то (бесконечный цикл, пока что-то не поменяется), и в нем не установлены задержки, то он сразу же загрузит процессор по завязку. Во избежание сего предусмотрена функция
которая добавляет паузу в милисекундах (ms, в данном примере равно 1 секунде) и устанавливает статус процеса «спящим / ожидающим».
Два скрипта (и больше), работающих паралельно, могут обмениваться данными между собой. Для обмена данными использованы файлы.
К примеру, первый скрипт (first) вызвал функцию
$PDT->write('Переменная', 'Значение');
$var = $PDT->read('first.Переменная');
$PDT->write('first.Переменная', 'Другое значение');
(этот пример есть в скриптах на GitHub)
Данные в скрипт можно передать через консоль. В таком случае скрипт должен прослушивать ввод
Примечание
Утилита писалась без мысли о том, чтоб выйти в мир, во всеобщее употребление. Выложил только из-за мысли, что кому-то может пригодиться.
Достаточно скинуть все содержимое в одну папку и зайти в нее через браузер.
Выполнение задачи в фоне php
Когда проект растет, появляются ресурсоемкие задачи обработки данных. Например, разбор xlsx (эксель) прайса для обновления цен, или ресайз большой фотографии. Случаи могут быть самыми разнообразными, когда мы не хотим, чтобы пользователь ждал, пока наш скрипт отработает.
В данном посте расскажу, как можно запускать фоновое выполнение PHP скриптов. Следует отметить, что без использования VPS (Virtual Private Server), то есть на обычном шаред хостинге, такой способ не сработает. Строго говоря, диспетчер создавался под Linux, хотя не исключена корректная работа и на FreeBSD. Даже скорее всего будет работать тоже.
В самом диспетчере ничего сложного нет, он скорее имеет больше заморочек по администрированию. Давайте сначала разберем, из чего он состоит со стороны ОС, какие используются команды:
Здесь мы запрашиваем список процессов с именем php. Остальные параметры влияют на отображение результатов, т.е. мы хотим видеть pid, и непосредственно полную команду, включая параметры. Пример вывода этой команды:
12769 php /path/to/1.php /path/to/price.xlsx
Таким образом, мы проверяем, запущена ли уже такая задача, и если запущена, то по умолчанию повторный запуск не производится. Если все же требуется параллельно запустить еще один экземпляр с точно такими же параметрами, можно это указать явным способом.
php -d max_execution_time=300 –f /pathto/1.php /pathto/price.xlsx > /dev/null 2>&1 & echo $!
Здесь мы запускаем php, причем указываем максимальное время выполнения в 300 секунд, чтобы наши фоновые процессы не превратились в бесконечные. Простая мера предосторожности, не более. Далее идет путь к скрипту, потом параметры, если есть необходимость. Перенаправляем вывод вместо stdout в «черную дыру», 2>&1 означает, что мы так же направляем вывод ошибок туда же. Кстати, можно перенаправить в файл через >>, в целях дебаггинга. Но советую отлаживать скрипты перед тем как их щапускать в фоне. Самое главное, последняя часть: & уходим в background и echo $! Как раз выводим (возвращаем) PID только что созданного процесса.
Это все, теперь сам код диспетчера и пример использования:
class backgrounder < public function launch($path = null, $timeout = 300, $once = true) < if (! file_exists(preg_replace('/^(\S+).*/', '$1', $path))) < throw new Exception('No such file to launch'); >if ($once === true) < exec('ps -C php -o pid=,command=', $output); foreach ($output as $row) < preg_match('/-f (.*)/', $row, $result); if (!empty($result[1]) && $result[1] == $path) < return false; >> > return exec('php -d max_execution_time=' . $timeout . ' -f ' . $path . ' > /dev/null 2>&1 & echo $!', $output); > public function isRunning($pid) < exec('ps -p ' . $pid . ' -o command=', $output); if (empty($output)) < return false; >if (preg_match('/php -d max_execution_time=/', $output[0])) < return true; >return false; > >
Допустим у нас есть php скрипт, который принимает в параметрах путь до файла, читает его и складывает содержимое в другой файл. Причем делает это очень долго (заменим на sleep 😉 )
//copyman.php //В массиве $argv все переданные параметры вызова if (empty($argv[1])) < exit(); >sleep(30); $content = file_get_contents($argv[1]); file_put_contents('ourfile.txt', $content);
А вот, как запускать его в фоновом режиме:
//index.php require_once 'backgrounder.php'; $bg = new backgrounder(); $pid = $bg->launch(__DIR__ . '/copyman.php exampledata.txt'); echo $pid;
Метод launch может принимать 3 параметра. Первый это путь к файлу-скрипту со всеми параметрами; второй – максимальное время выполнения (по умолчанию 300 секунд) и третий разрешено ли запускать копии, то есть точно такие же процессы с идентичными параметрами.
Обратите внимание, в переменную $pid мы вернули идентификатор процесса. Его можно где-нибудь сохранить, например, в куках пользователя, и при следующем открытии страницы узнать, закончилось ли выполнение задачи. Для этого метод isRunning
В ответ true или false соответственно.