Я немного узнал о порождении и мониторинге новых процессов из кода PHP, работая в Onebip и пытаясь внести свой вклад в Paratest . Вот что вам нужно знать, если вы считаете, что exec () или выполнение всего в одном скрипте .php всегда достаточно.
отказ
У вас должен быть строгий контроль над тем, сколько процессов вы создаете в ответ на определенные HTTP-запросы, а также за время их жизни: процессы Unix имеют большие издержки по сравнению с потоками Apache, которые обычно обрабатывают запросы в фиксированном количестве предварительно разветвленных процессов. ,
Более того, есть технологии, которые предлагают создавать, отслеживать и завершать эти процессы для вас, например Gearman. Здесь я опишу технологию внутри PHP, которая позволяет запрашивать новые процессы в операционной системе (возможно, в Linux).
Черные ящики
exec () является одним из самых популярных инструментов для выполнения внешних команд в их собственных процессах, эффективно эмулируя вашу консоль в течение десятков лет. В первом строковом аргументе exec () вы можете:
- установить переменные среды
- перенаправить каналы к файлам с> 2> или к другим командам с |
- передавать аргументы
exec () будет ожидать завершения процесса и заполнит свой второй аргумент выводом, возвращая при этом код завершения. Если вы хотите выполнить процесс в фоновом режиме, добавьте ‘&’ к командной строке.
Вот небольшой пример:
exec("VARIABLE=value /bin/command --option=value > log.txt 2> errors.txt", $output);
Обязательно используйте escapeshellarg (), если вы динамически создаете командные строки такого типа.
Контроль потоков
Когда связь между процессами не прекращается после нереста, но необходима в течение всего жизненного цикла процессов, exec () и его кузены не прерывают его.
proc_open () — инструмент, который вам нужен в этих случаях. Эта функция принимает команду и конфигурацию для трех потоков (Stdin, stdout и stderr), используемых в Unix в качестве стандартного интерфейса для связи. Он заполняет массив из 3 каналов, которые можно рассматривать как файловые дескрипторы: родительский процесс сможет читать или записывать их так же, как он работает с файлами, с расширением потока PHP. Этот стандартный интерфейс позволяет получать доступ к файлам, сокетам и другим процессам с помощью одних и тех же полиморфных функций: синтаксис является процедурным и примитивным, но потенциальным является ОО-подобный полиморфизм.
$configuration = array(); $child = proc_open('/usr/bin/grep proc', $configuration, $pipes); fwrite($pipes[0], "this is a test"); fwrite($pipes[0], "of proc_*() functions"); fclose($pipes[0]); stream_get_contents($pipes[1]);
Сложные или большие входные данные требуют использования стандартного ввода и вывода над аргументами (экранирование и цитирование аргументов не требуется). Более того, когда необходима двунаправленная связь, exec () просто возвращает вам выходные данные, а работа с процессами с помощью proc_open () позволяет осуществлять пинг-понг-обмен сообщениями.
Быть милым
Вы даже можете создать и убить группу детей с помощью proc_open (), которая унаследует приоритет (приятное значение) родительского процесса PHP. Тем не менее, мониторинг состояния детей, чтобы узнать, когда они могут получить больше информации, не является тривиальным.
Классическая спиннинговая петля:
while (1) { foreach ($this->processes as $p) { set_stream_blocking($p, 0); if ($line = fgets($p)) { // deal with it } } }
будет делить процессоры поровну между дочерними и родительскими процессорами, крадя ресурсы у первого, чтобы передать второму, чтобы просто запустить цикл навсегда. Каждый раз, когда функция foreach () завершается, родитель начинает новую серию проверок, и если у вас есть только 2 дочерних элемента, он может легко достичь 33% ЦП (66% на 2 ядрах и т. Д.).
Спать немного лучше:
while (1) { foreach ($this->processes as $p) { set_stream_blocking($p, 0); if ($line = fgets($p)) { // deal with it } } usleep(100000); // microseconds }
потому что родительский процесс фактически возвращает управление ОС в течение интервала времени, в течение которого он спит, и не использует ЦП. Однако он не масштабируется, поскольку необходимое время для сна не является детерминированным и не может быть легко определено родителем.
Мы не должны изобретать механизмы ОС:
while (1) { foreach ($this->processes as $p) { set_stream_blocking($p, 1); // is already the default if ($line = fgets($p)) { // deal with it } } }
Функция fgets () и другие функции, основанные на дескрипторах, будут блокироваться, пока в потоках не появятся данные.
Однако, если у вас есть популяция детей, это решение заставляет вас ждать первого, второго, третьего и т. Д.: Если первый процесс блокируется, вы не переходите к следующим, даже если они уже завершены.
Для работы с несколькими потоками правильное решение — stream_select (), которое будет блокироваться до тех пор, пока один из набора потоков не будет готов.
$read = array($stream1, $stream2); $write = array(); $except = array(); if (($num_changed_streams = stream_select($read, $write, $except, 0)) > 0) { // $read has been modified to contain the streams where reads now won't block }
Для чтения потоков это означает, что на них доступны данные, поэтому вызовы fread () не будут блокироваться.
Данные здесь определены как даже один байт, но если вы записываете потоки в виде отдельных фрагментов, таких как строки, они будут выходить в виде строк только на другом конце; использование fgets () для чтения вывода процессов строчной печати в сочетании с stream_select () фактически никогда не блокируется.
Запись потоков будет считаться готовой, когда процесс на другом конце заблокировал ожидание ввода.
Помните, что блокировка не является проблемой , является средством синхронизации для процессов UNIX. Процессы, которые не блокируют и не пытаются опрашивать ввод самостоятельно, обычно тратят ресурсы на то, что ОС уже реализовала бесплатно; Неблокирующие архитектуры, такие как Node.js, не основаны на нескольких процессах, которые должны ожидать друг друга.