Статьи

Внешние процессы и PHP

Я немного узнал о порождении и мониторинге новых процессов из кода 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, не основаны на нескольких процессах, которые должны ожидать друг друга.