Размножаем неблокирующие процессы с помощью proc_open ()
Пример
PHP не поддерживает одновременный запуск кода, если вы не установите расширения, такие как pthread
. Иногда это можно обойти, используя proc_open()
и stream_set_blocking()
и читая их вывод асинхронно.
Если мы разделим код на более мелкие куски, мы сможем запустить его как несколько процессов. Затем с помощью функции stream_set_blocking()
мы можем сделать каждый подпроцесс также неблокирующим. Это означает, что мы можем порождать несколько подпроцессов, а затем проверять их вывод в цикле (аналогично четному циклу) и ждать, пока все они не завершатся.
В качестве примера у нас может быть небольшой подпроцесс, который просто запускает цикл и в каждой итерации спит случайным образом в течение 100-1000 мс (обратите внимание, задержка всегда одинакова для одного подпроцесса).
<?php // subprocess.php $name = $argv[1]; $delay = rand(1, 10) * 100; printf("$name delay: ${delay}ms\n"); for ($i = 0; $i < 5; $i++) { usleep($delay * 1000); printf("$name: $i\n"); }
Затем основной процесс порождает подпроцессы и читает их вывод. Мы можем разделить его на более мелкие блоки:
- Порождем подпроцессы с
proc_open()
. - Делаем каждый подпроцесс неблокирующим с помощью
stream_set_blocking()
. - Запускаем цикл, пока все подпроцессы не закончат использование
proc_get_status()
. - Правильно закрываем дескрипторы файлов с выходным каналом для каждого подпроцесса с помощью
fclose()
и закрываем дескрипторы процессов с помощьюproc_close()
.
<?php // non-blocking-proc_open.php // Файловые дескрипторы для каждого подпроцесса. $descriptors = [ 0 => ['pipe', 'r'], // stdin 1 => ['pipe', 'w'], // stdout ]; $pipes = []; $processes = []; foreach (range(1, 3) as $i) { // Создать подпроцесс. $proc = proc_open('php subprocess.php proc' . $i, $descriptors, $procPipes); $processes[$i] = $proc; // Сделать подпроцесс неблокирующим (только выходной канал). stream_set_blocking($procPipes[1], 0); $pipes[$i] = $procPipes; } // Запускать в цикле, пока не завершатся все подпроцессы. while (array_filter($processes, function($proc) { return proc_get_status($proc)['running']; })) { foreach (range(1, 3) as $i) { usleep(10 * 1000); // 100ms // Прочитать весь доступный вывод (непрочитанный вывод буферизован). $str = fread($pipes[$i][1], 1024); if ($str) { printf($str); } } } // Закрыть все трубы и процессы. foreach (range(1, 3) as $i) { fclose($pipes[$i][1]); proc_close($processes[$i]); }
Выходные данные затем содержат смесь из всех трех подпроцессов, поскольку они считываются функцией fread() (обратите внимание, что в этом случае proc1
завершился намного раньше, чем два других):
$ php non-blocking-proc_open.php proc1 delay: 200ms proc2 delay: 1000ms proc3 delay: 800ms proc1: 0 proc1: 1 proc1: 2 proc1: 3 proc3: 0 proc1: 4 proc2: 0 proc3: 1 proc2: 1 proc3: 2 proc2: 2 proc3: 3 proc2: 3 proc3: 4 proc2: 4