当Perl发生警报时,应该如何清理挂起的孙子进程?
我有一个并行化的自动化脚本,需要调用许多其他脚本,其中一些脚本挂起,因为它们(不正确地)等待标准输入,或者等待其他不会发生的其他事情.这不是很大的事情,因为我赶上了
alarm的人.诀窍是当孩子关闭时关闭这些悬挂的孙子进程.我认为SIGCHLD,等待和进程组的各种咒语可以做到这一点,但是他们都阻止了,孙子没有收获.
我的解决方案,它的工作原理似乎不是正确的解决方案.我对Windows解决方案还没有特别的兴趣,但是我最终也需要这样做.我的工作只适用于Unix,现在很好. 我写了一个小脚本,它需要同时并行的孩子的数量来运行,并且总数是: $fork_bomb <parallel jobs> <number of forks> $fork_bomb 8 500 这可能会在几分钟内达到每用户进程限制.我发现的许多解决方案只是告诉你增加每个用户的进程限制,但是我需要运行大约30万次,这样就不会有效.同样,重新执行的建议等等,以清除流程表不是我需要的.我想实际解决问题,而不是在其上拍打胶带. 我查找进程表,查找子进程,并单独关闭SIGALRM处理程序中的挂起进程,该处理程序需要死机,因为其余的实际代码在此之后没有成功的希望.通过流程表的kludgey爬行不会让我从表现的角度来看待,但我不介意不要这样做: use Parallel::ForkManager; use Proc::ProcessTable; my $pm = Parallel::ForkManager->new( $ARGV[0] ); my $alarm_sub = sub { kill 9,map { $_->{pid} } grep { $_->{ppid} == $$} @{ Proc::ProcessTable->new->table }; die "Alarm rang for $$!n"; }; foreach ( 0 .. $ARGV[1] ) { print "."; print "n" unless $count++ % 50; my $pid = $pm->start and next; local $SIG{ALRM} = $alarm_sub; eval { alarm( 2 ); system "$^X -le '<STDIN>'"; # this will hang alarm( 0 ); }; $pm->finish; } 如果你想用完流程,拿出kill. 我以为设置一个进程组会工作,所以我可以一起杀死所有的东西,但是阻止: my $alarm_sub = sub { kill 9,-$$; # blocks here die "Alarm rang for $$!n"; }; foreach ( 0 .. $ARGV[1] ) { print "."; print "n" unless $count++ % 50; my $pid = $pm->start and next; setpgrp(0,0); local $SIG{ALRM} = $alarm_sub; eval { alarm( 2 ); system "$^X -le '<STDIN>'"; # this will hang alarm( 0 ); }; $pm->finish; } 与POSIX的setid同样的事情也没有起作用,我认为实际上以不同的方式打破了事情,因为我并没有真正地追踪它. 奇怪的是,Parallel::ForkManager的run_on_finish对于同样的清理代码来说太迟了:孙子们显然已经从这个时候与子进程分离了. 解决方法
我已经读了这个问题了几次,我想我得到了你的一些
正在努力做你有一个控制脚本.这个脚本产生了 孩子做一些东西,这些孩子产卵孙子 实际做工作.问题是孙子可以 太慢(等待STDIN或其他),你想杀死他们. 此外,如果有一个缓慢的孙子,你想要整个 孩子死亡(如果可能,杀死其他孙子孙). 所以,我试着实现这两种方式.第一个是做的 下一个策略是让父母产生孩子,然后 我们将使用EV来管理孩子和计时器,以及AnyEvent #!/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) = @_; 参数是当孩子完成时调用的回调 在这个功能中,我们需要fork.在父母中,我们设置一个孩子 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; 我们还需要知道什么时候完成所有的工作,以及是否 我也保持一个布尔值来指示错误状态.如果一个过程 # then start the kids my $done = AnyEvent->condvar; my $error = 0; $done->begin; (我们也开始计数1,所以如果有0个工作,我们的过程 现在我们需要为每个工作分叉,并运行这个工作.在父母中,我们 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 } 你也可以在这里关闭stdin,如果你想的话. 现在,在所有的过程都产生之后,我们等待他们 } # 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 好的,那就是孩子和孙子/工作.现在我们只需要写 像小孩一样,我们要用一个数字的condvar来等待我们 # main program my $all_done = AnyEvent->condvar; 我们需要一些工作去做.这是一个总是成功的,而且 my $good_grandchild = sub { exit 0; }; my $bad_grandchild = sub { my $line = <STDIN>; exit 0; }; 那么我们只需要开始小孩的工作.如果你记得的方式 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,$bad_grandchild); start_child $ok,($bad_grandchild,$bad_grandchild,$good_grandchild); 其中两个将超时,两个将成功.如果您按Enter键 无论如何,一旦开始,我们只需要等待他们 $all_done->recv; say "...done"; exit 0; 这就是程序. 有一件事我们没有做,Parallel :: ForkManager是 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; 这里的优点是你可以在孩子的时候做其他的事情 无论如何,希望这有帮助. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |