PCNTL, передача сигналов

GrayMaster

Новичок
PCNTL, передача сигналов

Добрый день !

Возникла такая проблема:
Главная программа создаёт 50 потомков, с помощью PCNTL.
Результат вполнение каждого потомка должен записаться в MySQL.

Чтоб записать результат, нужно передать данные от потомка в главную программу, причём передать нужно именно данные, а не сигнал.

Как это сделать ?

Сигналы я передаю так:
Код:
declare(ticks = 1);

define("SIG_SUCCESS", 50);
define("SIG_ERROR", 51);

function sig_handler($signo) {
        switch ($signo) {
            case SIG_SUCCESS:
                ...
                break;
            case SIG_ERROR:
                ...
                break;
        }
}

function child_exec($params, ...) {     
        ...
  
        if (...) {
                posix_kill($ppid, SIG_ERROR);
        }else {
                posix_kill($ppid, SIG_SUCCESS);
        }
}
 

svetasmirnova

маленький монстрик
>Чтоб записать результат, нужно передать данные от потомка в главную программу, причём передать нужно именно данные, а не сигнал.
Зачем?
 

GrayMaster

Новичок
Или хотя бы как определить в процедуре "sig_handler" PID процесса от которого пришёл сигнал...

-~{}~ 17.01.06 18:44:

Если я в каждом потоке буду создавать MySQL соединение - MySQL просто рухнет :)
Проверил - наткнулся на грабли...

См. пост выше - вот что мне нужно, так я смогу определить к чему относится принятый сигнал...

-~{}~ 17.01.06 18:48:

именно данные, а не сигнал
Нужно передать сигнал, и определить к какому потомку он относится, чтоб определить какие параметры мы передавали процедуре child_exec().
 

svetasmirnova

маленький монстрик
Сейчас не могу проверить насчёт соединения. Но потомок же наследует родительское окружение. По-моему, соединение одно будет.
 

Tor

Новичок
попробуй использовать shared memory
или файлы на худой конец
 

whirlwind

TDD infected, paranoid
case SIG_SUCCESS:
...
break;
case SIG_ERROR:
...
Если для этого, то [m]pcntl_wexitstatus[/m] через [m]pcntl_waitpid[/m]. Если майнтред должен работать, то помогут [m]pcntl_wifsignaled[/m], [m]pcntl_wstopsig[/m]. Вариантов куча и без шаредмемов.
 

svetasmirnova

маленький монстрик
>Это как ?
Так же как и с процессами. $result = exec('php -r concrete_file.php'); Родитель же их всё равно ждёт.
 

whirlwind

TDD infected, paranoid
>Нужно передать сигнал, и определить к какому потомку он относится,

а как можно отправить сигнал не зная pid-потомка?

> чтоб определить какие параметры мы передавали процедуре child_exec().

ну и что мешает после форка сделать

PHP:
$childs[$pid] = $args
и в обработчике SIGCHLD обрабатывать код завершения потомка и чистить $childs посредством waitpid?
 

Tor

Новичок
svetasmirnova
но форкнуть можно 50 штук и ждать их всех вместе
а ты предлагаешь ждать каждого
 

whirlwind

TDD infected, paranoid
PS. кроме того, от потомка к родителю можно передать посредством кода завершения, который потомок может указать через exit
 

GrayMaster

Новичок
2whirlwind:
>Нужно передать сигнал, и определить к какому потомку он относится,

а как можно отправить сигнал не зная pid-потомка?
Передать от потомка сигнал в главную программу, а там определить от какого потомка сигнал, и записать данные в MySQL.

Вобщем у меня такая ситуация... Я создаю 50 потомков, каждому передаю параметры, и в конце работы каждый потомок должен записать результат в MySQL. Для этого я хочу передать данные в основную программу, и там сделать запись.
Создать MySQL соединение в каждом потомке - не вариант.

Мой скрипт:
Код:
declare(ticks = 1);

define("SIG_SUCCESS", 50);
define("SIG_ERROR", 51);

function sig_handler($signo) {
        switch ($signo) {
            case SIG_SUCCESS:
                ...
                break;
            case SIG_ERROR:
                ...
                break;
        }
}

function child_exec($params, ...) {     
        ...
  
        if (...) {
                posix_kill($ppid, SIG_ERROR);
        }else {
                posix_kill($ppid, SIG_SUCCESS);
        }
}

pcntl_signal(SIG_SUCCESS, "sig_handler");
pcntl_signal(SIG_ERROR, "sig_handler");

$mysql = new mysql;
$mysql->connect(...);

while (...) {
                if (count($pids) >= 50) {
                        $child_pid = pcntl_waitpid(-1, $status);
                        unset($pids[array_search($child_pid, $pids)]);
                }

                $pid = pcntl_fork();

                if ($pid == -1) {
                        die("Could not fork!");
                } elseif ($pid) {
                        $pids[] = $pid;
                } else {
                        child_exec(.........);
                        exit();
                }
}

$mysql->close();
-~{}~ 17.01.06 20:59:

В child_exec() я передаю ID элемента некого массива из while (...).
Затем после выполнения потомка мне нужно выполнить:
update `base` set `success='1' where `id`='ПЕРЕДАННЫЙ_ID'
или
update `base` set `success='0' where `id`='ПЕРЕДАННЫЙ_ID'

В зависимости от результата выполнения потомка.
 

whirlwind

TDD infected, paranoid
>Передать от потомка сигнал в главную программу, а там определить от какого потомка сигнал, и записать данные в MySQL

Повторюсь - какой в этом смысл? По-нормальному это делается через обработку сигнала SIGCHLD. Там Вам и pid потомка и код завершения.

> потомок должен записать результат в MySQL

Скажите, что Вы хотите передавать родителю? И поверьте, любые извращения по передаче сложных данных между родственными процессами обойдутся дороже отточенного на низком уровне коннекта к СУБД.

Не верите, ну попробуйте действительно шаредмем, сокеты на худой конец, раз уж пайпов нет.

Форк приводит к дублированию всех данных, в том числе и потоков ввода/вывода. Другое дело, если Вы запускаете скрипт под апачем.

-~{}~ 17.01.06 18:06:

> я передаю ID элемента некого массива из while (...).
Затем после выполнения потомка мне нужно выполнить:

ну сопоставьте pid => ID после форка

$childs[$pid] = $ID

-~{}~ 17.01.06 18:20:

PS. Может непонятно? постараюсь объяснить


Обработчик сигнала SIGCHLD определяется в майнтреде.
Все порожденные майнтредом процессы принадлежат одной группе (т.к. в дочерних нет отрыва setsid). Когда происходит завершение работы потомка, майнтред получает сигнал SIGCHLD - а на нем обработчик. В этом обработчике посредством waitpid мы получаем идентификаторы завершенных потомков. Очень важно юзать waitpid, потому как эта функция вычищает таблицу процессов (зомбей в т.ч.). По идентификатору завершенного процесса мы можем получить код завершения потомка. ОЧЕНЬ ВАЖНО завершать работу потомка вызовом функции exit. В качестве аргумента функции можно указать целочисленное значение в пределах одного байта. Как правило, об успешном завершении свидетельствует код 0, а любой другой говорит об ошибках в работе. Т.к. сразу после форка в родителе мы записали pid=>id в хеш, мы получаем значение id и выполняем наш SQL запрос. После этого unset($childs[$pid])... и к следующему потомку, пока waitpid не скажет, что завершенные потомки кончились. После возврата из обработчика майнтред продолжает работу.

-~{}~ 17.01.06 18:25:

ЗЫ. В этой схеме есть один хороший плюс. На основе хеша $childs мы можем выполнить балансировку нагрузки. Грубо говоря, ограничить количество одновременно-выполняющихся потоков до определенной величины. Размер хеша $childs свидетельствует о количестве выполняемых потоков. Далее просто: как только count($childs) < $maxp, можем делать форк для следующей задачи.
 

GrayMaster

Новичок
Можно ещё вопросик...
PHP:
<?php
error_reporting(E_ALL);

function child_exec($id) {
        $ppid = posix_getppid();
        
        sleep(rand(1, 5));
}

$childs = array();
for ($i = 1; $i < 11; $i++) {
        if (count($childs) >= 3) {
                $child_pid = pcntl_waitpid(-1, $status);
                echo $childs[$child_pid]." -> ".$status."\n";
                unset($childs[$child_pid]);
        }

        $pid = pcntl_fork();

        if ($pid == -1) {
                die("Could not fork!");
        } elseif ($pid) {
                $childs[$pid] = $i;
        } else {
                child_exec($i);
                exit();
        }
}

 while (count($childs) > 0) {
        $child_pid = pcntl_waitpid(-1, $status);
        echo $childs[$child_pid]." -> ".$status."\n";
        unset($childs[$child_pid]);
}

echo "Finished!\n";
?>
Где мне указывать результат выполнения потомка ?
в exit(ПАРАМЕТР) - тут чтоли ? затем он передастся в $status. Так или нет ? :)
 

whirlwind

TDD infected, paranoid
Ну да, Так. только в таком варианте не совсем схема прозрачна.

во-первых
PHP:
       } else { 
                exit( child_exec($i) );
        }
..
PHP:
function child_exec($id) { 
       ...
       return $errors ? 1 : 0;
}
во-вторых

pcntl_waitpid(-1,$status,WNOHANG);

и в третьих


убираем waitpid из цикла в обработчик сигнала SIGCHLD (см. посты выше).


[UPD]Хм.. хотя нет, не стоит. Будет лучше доработать ваш вариант. Как раз waitpid с блокировкой поможет организовать цикл ожидания завершения потомков, в случае если достигли максимума.


Результат завершения потомка (т.е. return $errors ? 1 : 0;)
получаем посредством [m]pcntl_wexitstatus[/m] в обработчике SIGCHLD. Только обратите внимание на работу [m]pcntl_wexitstatus[/m], есть тонкости.

>затем он передастся в $status. Так или нет ?

pcntl_waitpid() will store status information in the status parameter which can be evaluated using the following functions: pcntl_wifexited(), pcntl_wifstopped(), pcntl_wifsignaled(), pcntl_wexitstatus(), pcntl_wtermsig() and pcntl_wstopsig().
-~{}~ 18.01.06 11:26:

вот так будет хорошо
PHP:
for ($i = 1; $i < 11; $i++) { 

     // fork;

     if ( count($childs) < $maxp )
          continue;

     // waitpid без WNOHANG
}
 

GrayMaster

Новичок
Большое спасибо, во всем разобрался - работает :)

PS. Я считаю лучше обойтись без "continue" - т.к. этот оператор "ломает" стуктуру программы...

PHP:
for ($i = 1; $i < 11; $i++) {  
     // fork; 

     if (count($childs) >= 3) {
          // waitpid
     }
}
Ещё раз большое спасибо ;)
 
Сверху