Я прочитал вопрос несколько раз, и я думаю, что понимаю, что вы
пытаемся сделать. У вас есть контрольный скрипт. Этот скрипт порождает
дети делают что-то, и эти дети порождают внуков
на самом деле делать работу. Проблема в том, что внуки могут быть
слишком медленно (в ожидании STDIN или чего-то еще), и вы хотите их убить.
Кроме того, если есть один медленный внук, вы хотите весь
умирает ребенок (по возможности убивая других внуков).
Итак, я попытался реализовать это двумя способами. Первым было сделать
parent порождает потомка в новом сеансе UNIX, установите таймер на несколько
секунд, и убить весь дочерний сеанс, когда таймер выключился.
Это сделало родителей ответственными как за ребенка, так и за
внуки. Это также не сработало правильно.
Следующая стратегия состояла в том, чтобы родитель породил ребенка, а затем
возложить на ребенка ответственность за управление внуками. Было бы
установить таймер для каждого внука и убить его, если процесс не
выход по времени истечения. Это прекрасно работает, поэтому вот код.
Мы будем использовать EV для управления детьми и таймерами, а AnyEvent для
API. (Вы можете попробовать другой цикл событий AnyEvent, такой как Event или POE.
Но я знаю, что ЭВ правильно справляется с условием, когда ребенок выходит
прежде чем вы скажете петле контролировать его, что устраняет надоедливую гонку
условия, к которым уязвимы другие циклы.)
#!/usr/bin/env perl
use strict;
use warnings;
use feature ':5.10';
use AnyEvent;
use EV; # you need EV for the best child-handling abilities
Нам нужно следить за детскими наблюдателями:
# active child watchers
my %children;
Тогда нам нужно написать функцию для запуска детей. Вещи
порождение родителей называется детьми, а вещи детьми
икру называют рабочими местами.
sub start_child($$@) {
my ($on_success, $on_error, @jobs) = @_;
Аргументы - это обратный вызов, который вызывается, когда дочерний объект завершает
успешно (то есть его работа также была успешной), обратный вызов, когда
ребенок не завершил успешно, а затем список coderef
задания для запуска.
В этой функции нам нужно форк. В родительском мы настраиваем дочерний
наблюдатель для наблюдения за ребенком:
if(my $pid = fork){ # parent
# monitor the child process, inform our callback of error or success
say "$$: Starting child process $pid";
$children{$pid} = AnyEvent->child( pid => $pid, cb => sub {
my ($pid, $status) = @_;
delete $children{$pid};
say "$$: Child $pid exited with status $status";
if($status == 0){
$on_success->($pid);
}
else {
$on_error->($pid);
}
});
}
В детстве мы фактически выполняем задания. Это включает в себя немного
настройка, хотя.
Во-первых, мы забываем о наблюдателях за детьми родителей, потому что это не делает
смысл для ребенка быть информированным о выходе его братьев и сестер. (Вилка
весело, потому что вы наследуете все родительское состояние, даже когда это
не имеет никакого смысла.)
else { # child
# kill the inherited child watchers
%children = ();
my %timers;
Нам также нужно знать, когда все работы выполнены, и действительно ли
все они были успешными. Мы используем счетную условную переменную для
определить, когда все вышло. Мы увеличиваем при запуске, и
уменьшение при выходе, и когда счетчик равен 0, мы знаем, что все сделано.
Я также сохраняю логическое значение для индикации состояния ошибки. Если процесс
выходит с ненулевым статусом, ошибка переходит в 1. В противном случае, он остается 0.
Возможно, вы захотите сохранить больше состояния, чем это:)
# then start the kids
my $done = AnyEvent->condvar;
my $error = 0;
$done->begin;
(Мы также начинаем отсчет с 1, чтобы при наличии 0 заданий наш процесс
все еще выходит.)
Теперь нам нужно выполнить форк для каждого задания и запустить его. В родительском мы
сделать несколько вещей. Мы увеличиваем condvar. Мы устанавливаем таймер, чтобы убить
ребенок, если он слишком медленный. И мы настраиваем детского наблюдателя, чтобы мы могли
быть информированным о состоянии выхода на работу.
for my $job (@jobs) {
if(my $pid = fork){
say "[c] $$: starting job $job in $pid";
$done->begin;
# this is the timer that will kill the slow children
$timers{$pid} = AnyEvent->timer( after => 3, interval => 0, cb => sub {
delete $timers{$pid};
say "[c] $$: Killing $pid: too slow";
kill 9, $pid;
});
# this monitors the children and cancels the timer if
# it exits soon enough
$children{$pid} = AnyEvent->child( pid => $pid, cb => sub {
my ($pid, $status) = @_;
delete $timers{$pid};
delete $children{$pid};
say "[c] [j] $$: job $pid exited with status $status";
$error ||= ($status != 0);
$done->end;
});
}
Использование таймера немного легче, чем тревога, так как он несет
заявить с этим. Каждый таймер знает, какой процесс убить, и это легко
отменить таймер при успешном завершении процесса - мы просто
удалите его из хеша.
Это родитель (ребенка). Ребенок (ребенка; или
работа) действительно просто:
else {
# run kid
$job->();
exit 0; # just in case
}
Вы также можете закрыть здесь стандартный ввод, если хотите.
Теперь, после того, как все процессы были созданы, мы ждем их
все выход, ожидая на condvar. Цикл событий будет контролировать
дети и таймеры, и поступайте правильно для нас:
} # this is the end of the for @jobs loop
$done->end;
# block until all children have exited
$done->recv;
Тогда, когда все дети вышли, мы можем сделать любую уборку
работа, которую мы хотим, например:
if($error){
say "[c] $$: One of your children died.";
exit 1;
}
else {
say "[c] $$: All jobs completed successfully.";
exit 0;
}
} # end of "else { # child"
} # end of start_child
ОК, это ребенок и внук / работа. Теперь нам просто нужно написать
родитель, который намного проще.
Как и ребенок, мы будем использовать счетчик, чтобы дождаться нашего
дети.
# main program
my $all_done = AnyEvent->condvar;
Нам нужно кое-что сделать. Вот тот, который всегда успешен, и
тот, который будет успешным, если вы нажмете возврат, но потерпит неудачу, если вы
просто пусть его убьет таймер:
my $good_grandchild = sub {
exit 0;
};
my $bad_grandchild = sub {
my $line = <STDIN>;
exit 0;
};
Итак, нам просто нужно начать работу с детьми. Если вы помните способ
к началу start_child
, требуется два обратных вызова, ошибка
обратный вызов и обратный вызов успеха. Мы настроим их; Ошибка
обратный вызов выведет «не в порядке» и уменьшит condvar, а
Успешный обратный вызов выведет «ок» и сделает тоже самое Очень просто.
my $ok = sub { $all_done->end; say "$$: $_[0] ok" };
my $nok = sub { $all_done->end; say "$$: $_[0] not ok" };
Тогда мы можем начать группу детей с еще большим количеством внуков.
работы:
say "starting...";
$all_done->begin for 1..4;
start_child $ok, $nok, ($good_grandchild, $good_grandchild, $good_grandchild);
start_child $ok, $nok, ($good_grandchild, $good_grandchild, $bad_grandchild);
start_child $ok, $nok, ($bad_grandchild, $bad_grandchild, $bad_grandchild);
start_child $ok, $nok, ($good_grandchild, $good_grandchild, $good_grandchild, $good_grandchild);
У двух из них истечет время ожидания, а у двух все получится. Если вы нажмете ввод
хотя, пока они бегут, все они могут преуспеть.
В любом случае, как только они начнутся, нам просто нужно подождать, пока они
отделка:
$all_done->recv;
say "...done";
exit 0;
И это программа.
Единственное, чего не делает Parallel :: ForkManager, это
«ограничить скорость» наших вилок, чтобы только 1071 ребенок бегал с
время. Это довольно легко реализовать вручную, хотя:
use Coro;
use AnyEvent::Subprocess; # better abstraction than manually
# forking and making watchers
use Coro::Semaphore;
my $job = AnyEvent::Subprocess->new(
on_completion => sub {}, # replace later
code => sub { the child process };
)
my $rate_limit = Coro::Semaphore->new(3); # 3 procs at a time
my @coros = map { async {
my $guard = $rate_limit->guard;
$job->clone( on_completion => Coro::rouse_cb )->run($_);
Coro::rouse_wait;
}} ({ args => 'for first job' }, { args => 'for second job' }, ... );
# this waits for all jobs to complete
my @results = map { $_->join } @coros;
Преимущество здесь в том, что вы можете делать другие вещи, пока ваши дети
работают - просто создайте больше потоков с async
, прежде чем делать
блокировка соединения. У вас также есть гораздо больше контроля над детьми
с AnyEvent :: Subprocess - вы можете запустить ребенка в Pty и кормить
это стандартный ввод (как с Expect), и вы можете захватить его стандартный ввод и стандартный вывод
и stderr, или вы можете игнорировать эти вещи, или что угодно. Вы получаете к
решите, а не какой-нибудь автор модуля, который пытается сделать вещи "простыми".
В любом случае, надеюсь, это поможет.