PHP разветвление и несколько дочерних сигналов - PullRequest
8 голосов
/ 16 февраля 2010

Я пытаюсь написать скрипт, который создает несколько разветвленных дочерних процессов с использованием pcntl_* функций .

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

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

define(ticks = 1);

$openProcesses = 0; // how many we have open
$max = 3;           // the most we want open at a time

pcntl_signal(SIGCHLD, "childFinished");

while (!time_is_up()) {
    if (there_is_something_to_do()) {
        $pid = pcntl_fork();
        if (!$pid) {      // I am the child
            foo();        //   run the long-running task
            exit(0);      //   and exit
        } else {          // I am the parent
            ++$openProcesses;
            if ($openProcesses >= $max) {
                pcntl_wait($status);    // wait for any child to exit 
            }                           // before continuing
        }
    } else {
        sleep(3);
    }
}

function childFinished($signo) {
    global $openProcesses;
    --$openProcesses;
}

В большинстве случаев это работает довольно хорошо, за исключением случаев, когда два или более процессов завершаются одновременно - функция обработчика сигнала вызывается только один раз, что исключает мой счетчик. Причина этого объясняется «Anonymous» в примечаниях руководства по PHP :

Несколько детей возвращают меньше, чем количество детей, выходящих в данный момент. Сигналы SIGCHLD - нормальное поведение для систем Unix (POSIX). SIGCHLD может читаться как «один или несколько детей изменили статус - иди осматривай своих детей и собирай их значения статуса».

У меня такой вопрос: Как я могу проверить детей и получить их статус? Есть ли надежный способ проверить, сколько дочерних процессов открыто в любой момент времени?

Использование PHP 5.2.9

Ответы [ 3 ]

2 голосов
/ 09 апреля 2015

Один из способов - сохранить массив PID дочерних процессов, а в обработчике сигналов проверить каждый PID, чтобы убедиться, что он все еще работает. (Непроверенный) код будет выглядеть так:

define(ticks = 1);

$openProcesses = 0; 
$procs = array();
$max = 3;

pcntl_signal(SIGCHLD, "childFinished");

while (!time_is_up()) {
    if (there_is_something_to_do()) {
        $pid = pcntl_fork();
        if (!$pid) {      
            foo();        
            exit(0);      
        } else {          

            $procs[] = $pid; // add the PID to the list

            ++$openProcesses;
            if ($openProcesses >= $max) {
                pcntl_wait($status);     
            }                           
        }
    } else {
        sleep(3);
    }
}

function childFinished($signo) {

    global $openProcesses, $procs;

    // Check each process to see if it's still running
    // If not, remove it and decrement the count
    foreach ($procs as $key => $pid) if (posix_getpgid($pid) === false) {
        unset($procs[$key]);
        $openProcesses--;
    }

}
0 голосов
/ 04 июля 2018

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

Использование функций pcntl_w * будет вашим другом здесь, и вы, вероятно, захотите внедрить процессный жнец. Документация не очень полезна и не содержит полезных примеров.

Это будет процесс, состоящий из нескольких частей:

1 - использовать pcntl_signal для отправки захваченных сигналов вашему обработчику сигналов

2 - Выполните ваш цикл / опрос и внутри этого цикла;

3 - Итерация по массиву ваших детей (который вы создадите ниже) и пожинайте их при необходимости

4 - fork (): это будет состоять из следующего:

pcntl_async_signals(true);

$children = array();
while ($looping === true)
{
    reapChildren();
    if (($pid = pcntl_fork()) exit (1); // error
    elseif ($pid) // parent
    { 
        $children[] = $pid;
        // close files/sockets/etc
        posix_setpgid ($pid,posix_getpgrp());
    }
    else
    { // child
        posix_setpgid(posix_getpid(),posix_getppid());
        // ... jump to child function/object/code/etc ...
        exit (0); // or whatever code you want to return
    }
} // end of loop

В жатке вам понадобится следующее:

function reapChildren()
{
    global $children;
    foreach ($children as $idx => $pid)
    {
        $rUsage = array();
        $status = 0; // integer which will be used as the $status pointer
        $ret = pcntl_waitpid($pid, $status, WNOHANG|WUNTRACED, $rUsage);
        if (pcntl_wifexited($status)) // the child exited normally
        {
            $exitCode = pcntl_wexitstatus($status); // returns the child exit status
        }
        if (pcntl_wifsignaled($status))  // the child received a signal
        {
            $signal = pcntl_wtermsig($status); // returns the signal that abended the child
        }
        if (pcntl_wifstopped($status))
        {
            $signal = pcntl_wstopsig($status); // returns the signal that  stopped the child
        }
    }
}

Приведенный выше код жнеца позволит вам опрашивать статус ваших детей, и если вы используете php7 +, массив $ signalInfo, который заполняется в вашем обработчике сигналов, будет содержать много полезной информации, которую вы можете использовать .. var_dump it .. проверить это. Кроме того, использование pcntl_async_signals (true) в php7 + заменяет необходимость объявления (ticks = 1) и ручного вызова pcntl_signal_dispatch ();

Надеюсь, это поможет.

0 голосов
/ 16 февраля 2010

Дети могут отправить SIGUSR1 родителю при запуске, а затем SIGUSR2 перед выходом. Другая вещь, с которой вы сталкиваетесь при использовании примитивных сигналов - это ядро, объединяющее их, что не происходит с RT-сигналами. Теоретически, ЛЮБОЙ не-rt сигнал может быть объединен.

Вы можете реализовать какую-то простую блокировку с использованием sqlite, когда только один дочерний элемент может иметь говорящую палку. Просто убедитесь, что дети обрабатывают нормально фатальные сигналы, чтобы они остались живы, чтобы освободить замок.

...