当 Perl 中发生警报时,我应该如何清理挂起的孙进程?

2024-02-01

我有一个并行自动化脚本,需要调用许多其他脚本,其中一些脚本挂起,因为它们(错误地)等待标准输入或等待各种其他不会发生的事情。这没什么大不了的,因为我抓住那些alarm http://perldoc.perl.org/functions/alarm.html。诀窍是在子进程关闭时关闭那些挂起的孙进程。我想到了各种咒语SIGCHLD、等待和进程组可以做到这一点,但它们都会阻塞,并且孙子不会被收获。

我的解决方案虽然有效,但似乎不是正确的解决方案。我目前对 Windows 解决方案还不是特别感兴趣,但我最终也会需要它。我的只适用于 Unix,目前还好。

我编写了一个小脚本,它获取同时运行的并行子进程的数量和分叉的总数:

 $ fork_bomb <parallel jobs> <number of forks>

 $ fork_bomb 8 500

这可能会在几分钟内达到每个用户进程的限制。我发现的许多解决方案只是告诉您增加每个用户的进程限制,但我需要它运行大约 300,000 次,所以这是行不通的。同样,重新执行等清除进程表的建议也不是我所需要的。我想真正解决问题,而不是用胶带包裹它。

我爬行进程表寻找子进程并在中单独关闭挂起的进程SIGALRMhandler,它需要终止,因为此后其余的实际代码没有成功的希望。从性能的角度来看,对进程表的复杂爬行并不会打扰我,但我不介意不这样做:

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 http://perldoc.perl.org/functions/kill.html.

我认为设置一个进程组会起作用,这样我就可以一起杀死所有东西,但这会阻止:

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 http://perldoc.perl.org/POSIX.html's setsid也不起作用,而且我认为这实际上以不同的方式破坏了事情,因为我并没有真正将其恶魔化。

奇怪的是,并行::ForkManager http://search.cpan.org/dist/Parallel-ForkManager's run_on_finish对于相同的清理代码来说发生得太晚了:此时孙子进程显然已经与子进程解除关联。


我已经读过这个问题好几次了,我想我已经明白你的意思了 正在努力做。你有一个控制脚本。这个脚本产生 孩子们去做一些事情,这些孩子会产生孙子 真正做这项工作。问题是孙子可以 太慢了(等待 STDIN,或者其他什么),你想杀死它们。 此外,如果有一个缓慢的孙子,您想要整个 孩子死(如果可能的话,杀死其他孙子)。

所以,我尝试通过两种方式实现这一点。第一个是让 父进程在一个新的 UNIX 会话中生成一个子进程,为几个进程设置一个计时器 秒,并在计时器关闭时终止整个子会话。 这使得父母既要对孩子负责,也要对孩子负责。 孙子们。它也没有正常工作。

下一个策略是让父进程生成子进程,然后 让孩子负责管理孙子。它会 为每个孙子设置一个计时器,如果进程没有设置则终止它 过期时间退出。这很好用,所以这是代码。

我们将使用 EV 来管理子项和计时器,并使用 AnyEvent 来管理 API。 (您可以尝试另一个 AnyEvent 事件循环,例如 Event 或 POE。 但我知道 EV 正确处理了孩子退出的情况 在你告诉循环监视它之前,这消除了恼人的竞争 其他循环容易受到影响的条件。)

#!/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列表 要运行的作业。

在这个函数中,我们需要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;

我们还需要知道所有工作何时完成,以及是否完成 他们都很成功。我们使用计数条件变量来 确定一切都已退出的时间。我们在启动时递增,并且 退出时递减,当计数为 0 时,我们知道一切都完成了。

我还保留一个布尔值来指示错误状态。如果一个进程 以非零状态退出,错误变为 1。否则,它保持 0。 您可能想要保留比这更多的状态:)

        # 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
            }

如果您愿意,也可以在此处关闭标准输入。

现在,在所有进程都生成之后,我们等待它们 全部通过等待 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

好的,这就是孩子和孙子/工作。现在我们只需要写 父母,这要容易得多。

像孩子一样,我们将使用计数 condvar 来等待我们的 孩子们。

# main program
my $all_done = AnyEvent->condvar;

我们需要做一些工作。这是一个总是成功的,并且 如果你按下回车键就会成功,但如果你按下回车键就会失败 就让它被定时器杀死吧:

my $good_grandchild = sub {
    exit 0;
};

my $bad_grandchild = sub {
    my $line = <STDIN>;
    exit 0;
};

那么我们只需要启动子作业即可。如果你还记得方法 回到顶部start_child,需要两次回调,一个错误 回调,以及成功回调。我们将进行设置;错误 回调将打印“not ok”并递减 condvar,并且 成功回调将打印“ok”并执行相同的操作。很简单。

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 所做的一件事是 “速率限制”我们的分叉,以便仅n孩子们正在跑步 时间。不过,这很容易手动实现:

 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,或者你可以忽略这些东西,或者其他什么。你可以到 决定,而不是某个试图让事情变得“简单”的模块作者。

无论如何,希望这会有所帮助。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

当 Perl 中发生警报时,我应该如何清理挂起的孙进程? 的相关文章

  • 我需要一个便携式、一致的伪随机数生成器

    I am writing a kid sister encryption http enfranchisedmind com blog posts the kid sister crypto manifesto function and I
  • 命令行参数中的“-”(破折号)有什么魔力?

    例子 创建 ISO 映像并将其直接刻录到 CD mkisofs V Photos r home vivek photos cdrecord v dev dev dvdrw 更改到上一个目录 cd 侦听端口 12345 并解压发送到该端口的数
  • Perl OO 方法调用第一个参数值 (->)

    就 Perl OO 而言 到底做了什么 gt do 例如我拨打 main 电话 result a b gt mymethod 在我定义的包中mymethod 我使用以下内容 my class 总的来说 我显然没有向mymethod 那么哪里
  • 如何从 Perl 调用 gnuplot 脚本

    我有一个 gnu gp 文件 grphist conf set terminal canvas Terminal type set to canvas Options are solid butt size 600 400 fsize 10
  • Perl - HTTP::代理捕获 XHR/JSON 通信

    网站http openbook etoro com main http openbook etoro com main 有一个实时提要 由 javascript 通过 XHR keep alive 请求生成 并以 gzip 压缩 JSON
  • Perl Mongo 查找对象 ID

    你会认为这是一件简单的事情 我有一个集合中的对象 ID 列表 我想根据对象 ID 获取单个记录 谷歌搜索过 但没有任何帮助 所以我有对象 ID 5106c7703abc120a04070b34 my client MongoDB Mongo
  • Bash 中 $() 和 () 之间的区别

    当我打字时ls l echo file 支架的输出 这只是简单的回显 被获取并传递到外部ls l命令 就等于简单的ls l file 当我打字时ls l echo file 我们有错误 因为不能嵌套 内部外部命令 有人可以帮助我理解之间的区
  • Linux 中 AF_UNIX 数据报消息的最大大小是多少?

    目前我已达到 130688 字节的硬限制 如果我尝试在一条消息中发送更大的内容 我会收到一条消息ENOBUFS error 我已经检查过net core rmem default net core wmem default net core
  • 调用 Perl 子程序时使用 & 符号和括号

    usr bin perl sub t print in t n print n s sub s print in s n print n t 1 2 print out n print n Output in t 1 2 in s 1 2
  • 如何使用 Perl 从 NCBI 获取 FASTA 核苷酸格式的基因特征?

    我可以手动下载 FASTA 文件 如下所示 gt lcl CR543861 1 gene 1 ATGCTTTGGACA gt lcl CR543861 1 gene 2 GTGCGACTAAAA 通过单击 发送到 并选择 基因特征 FAST
  • perl xs - 从 c 数组返回 perl 数组

    使用 XS 我尝试将值从 C 数组传递到可在脚本中使用的 Perl 数组 这是我的 xs 文件中的代码 AV DoubleArray getPerlArray CODE r newAV for size t i 0 i lt THIS gt
  • 如何设置 $!在 Perl 中

    我想在 perl 中编写一些设置 的函数 与内置 perl 函数类似 当我尝试执行此操作时 它抱怨 参数 无法创建管理员用户 在标量分配中不是数字 我试过用谷歌搜索这个 但不幸的是谷歌不会在 所以结果很难得到 if createUser a
  • 多个与单个 Catalyst 应用程序

    我有多个作为 FCGI 运行的 Catalyst 应用程序 将它们整合为具有多个控制器的单个控制器是否有好处 Thanks Simone 内存 大概吧 我认为每台服务器至少要保留 15MB 左右 因此如果您在 3 台服务器上运行 3 个应用
  • 从日志尾部提取匹配行后退出

    我使用范围运算符来提取日志文件的一部分 例如 tail F logfile perl ne print if b d 现在 一旦提取的部分匹配 我就尝试退出该过程 我尝试过 tail F logfile perl ne print if b
  • 如何在 BEGIN 块之外正确声明哈希?

    考虑这个简单的程序 您能解释一下为什么在取消注释前两行后输出会有所不同吗 我的哈希发生了什么use strict 如何修复程序以供使用use strict echo e key1 nkey2 nkey3 perl lne use stric
  • Perl 导入到我的所有包中?

    我倾向于经常使用 Data Dumper 最终我的 pl 代码中每个包的顶部都有以下样板 use strict use warnings use Data Dumper Data Dumper Deparse 1 Data Dumper P
  • 设置 Pyenv 的路径

    我正在尝试在我的服务器中设置 Pyenv 的加载路径 bashrc file 我正在关注这个tutorial https realpython com intro to pyenv 它要求我们在哪里设置pyenv到加载路径 然而 在我的 b
  • 如何在 Perl 脚本中包含另一个文件中的函数?

    这似乎是一个非常简单的问题 但不知何故 我的 Google Fu 失败了 在 Perl 中包含其他文件中的函数的语法是什么 我正在寻找类似C的东西 include blah h 我看到了使用 Perl 模块的选项 但这似乎需要对我当前的代码
  • 在C语言中如何清屏? [复制]

    这个问题在这里已经有答案了 我想清除屏幕上的所有文字 我尝试过使用 include
  • 为什么 Linux 对目录使用 getdents() 而不是 read()?

    我浏览 K R C 时注意到 为了读取目录中的条目 他们使用了 while read dp gt fd char dirbuf sizeof dirbuf sizeof dirbuf code Where dirbuf是系统特定的目录结构

随机推荐