Разветвление PHP и несколько дочерних сигналов

Я пытаюсь написать скрипт, который создает несколько разветвленных дочерних процессов, используя 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;
}

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

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

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

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


person nickf    schedule 16.02.2010    source источник
comment
вероятно, простое использование rabbitmq.com сделает все это гораздо менее подверженным ошибкам   -  person Pawel Dubiel    schedule 24.09.2016


Ответы (3)


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

declare(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--;
    }

}
person wavemode    schedule 09.04.2015

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

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

person Tim Post♦    schedule 16.02.2010

Я знаю, что это опоздало примерно на 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();

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

person That one guy from the movie    schedule 04.07.2018