如何在后台执行特定的子程序代码并从中获取结果?

时间:2016-11-27 12:56:48

标签: perl

如何在后台执行特定的子程序代码?

我想并行触发下面ping子程序的几个实例。我知道它之前已被问过,但我无法找到解决这个问题的方法。我发现的例子让我使用了fork,但是我想在后台只执行子程序代码,我需要在主代码中稍后获取子程序结果。

非常感谢。

#!/usr/bin/perl
use strict;
use warnings;

my $ipfile = "ips.txt";
open (my $fh, '<', $ipfile) or die "I couldn't open file  $ipfile\n";
chomp (my @ips = <$fh>);
close $fh;
my %pingResult;
foreach my $ip (@ips) {
    ping ($ip);
}

foreach my $ip (keys %pingResult) {
    print "ping result for $ip: $pingResult{$ip}\n";
}

sub ping {
    my $ip = $_[0];
    $pingResult{$ip} = `/sbin/ping -t 1 -c 1 $ip | grep packet`;
    chomp ($pingResult{$ip});
}

3 个答案:

答案 0 :(得分:3)

Parallel::ForkManager模块提供了从子进程返回到父进程的数据。它的工作原理是将其序列化并写入临时文件

Parallel::ForkManager->new调用的第二个参数必须是可以存储这些临时文件的目录的路径,而子项finish调用的第二个参数必须是引用< / em>到应返回的标量值。标量值可以是简单的字符串或数字,或者如果必须提供复杂的结构,它可以是对散列或数据的引用

要收集返回数据,您必须定义一个run_on_finish回调,用于收集有关终止子进程的信息

在这种情况下,我的ips.txt文件只包含八个字母AH,我使用了此代码

sleep rand 5 + 2;
my $ping = rand 10;

代表ping行动。返回值是随机数$ping。每个子节点的 ident @ip_addresses数组的索引,子节点和父节点都可以使用此值来标识子节点已处理的地址

您可能希望返回非零退出状态值(finish调用的第一个参数),以便父级可以判断ping是否完全失败。它可以从$exit_code回调

run_on_finish参数中获得
use strict;
use warnings 'all';

use Parallel::ForkManager;
use Cwd 'cwd';

use constant IP_FILE => 'ips.txt';

my @ip_addresses;
{
    open my $fh, '<', IP_FILE or die $!;
    @ip_addresses = <$fh>;
    chomp @ip_addresses;
}

my %ping_results;

my $pfm = Parallel::ForkManager->new(10, cwd);

$pfm->run_on_finish( sub {
    my ($pid, $exit_code, $ident, $exit_signal, $dump, $data) = @_;

    $ping_results{$ip_addresses[$ident]} = $$data;
});

for my $ident ( 0 .. $#ip_addresses ) {
    my $pid = $pfm->start($ident);
    next if $pid;

    sleep rand 5 + 2;
    my $ping = rand 10;

    $pfm->finish(0, \$ping);
}

$pfm->wait_all_children;

use Data::Dump;
dd \%ping_results;

输出

{
  A => 4.40219991930888,
  B => 2.82913053498731,
  C => 3.34837183912413,
  D => 3.39050637182908,
  E => 6.6558553334059,
  F => 6.72843905721919,
  G => 4.73434782211797,
  H => 3.30697605942504,
}

答案 1 :(得分:1)

有些人认为threads危险而丑陋。但特别是与Thread::Queue一起,我喜欢他们的优雅:

#!/usr/bin/env perl

use strict;
use warnings;
use Data::Dumper;

use threads;
use threads::shared;
use Thread::Queue;

use constant NUM_THREADS => 2;

my $workitems = Thread::Queue->new();
my %pingResult : shared;

sub main
{
    # create 2 worker threads
    threads->create( \&ping ) foreach ( 1 .. NUM_THREADS );

    # put the IPs into our working queue:
    my @ips = qw(127.0.0.1 stackoverflow.com localhost);
    $workitems->enqueue(@ips);
    $workitems->end();

    # wait for the threads to finish:
    $_->join() foreach ( threads->list() );

    print Data::Dumper::Dumper( \%pingResult );
}

sub ping
{
    while ( my $ip = $workitems->dequeue() ) {
        my $result = `/bin/ping -t 1 -c 1 $ip | grep packet`;
        chomp($result);

        lock(%pingResult);
        $pingResult{$ip} = $result;
    }
}

main();

使用T::Q::enqueue(),您可以在队列中放置“项目”,在本例中为IP地址。在此示例中,您可以从主线程执行此操作。完成队列填写后,请致电T::Q::end()

T::Q::dequeue()(从线程ping调用)阻塞,直到可以从队列中读取和删除项目。 ping函数然后调用/[s]bin/ping并将结果放入全局哈希%pingResult。变量标记为shared,这意味着它在所有线程之间共享。因此,您需要通过lock函数保护它免受并发访问。它阻止,直到没有其他人持有锁。当锁超出范围时,锁会自动解锁,即:在while循环的每次迭代之后。 (没有unlock功能。)

第二个ping线程完全相同,它是一种随机的线程处理哪个IP地址。他们都从队列中选择IP,直到T::Q::dequeue()返回一些假值,然后退出。

调用T::Q::end()取消阻止对T::Q::dequeue()的所有调用(无论队列是否为空),从而最终结束线程中的while循环。

顺便说一句:虽然T::Q在所有线程(包括main)之间共享,但是没有必要将其标记为共享或锁定它,因为它具有内置的线程安全性并完成所有这些工作。

除了为结果使用全局共享变量之外,您还可以创建第二个结果队列,让ping-threads将结果放在那里,让主线程逐个出列。

注意:使用线程时,为每个工作项(IP地址)创建新线程要好得多,但要事先创建N个线程,然后再提供它们通过队列工作。线程创建很昂贵。 T :: Q模块非常适合这种工作线程模型。

答案 2 :(得分:0)

您需要的是IO::Pipe::Producer。它专为此问题而设计。您必须对子进行调整才能打印结果,父脚本可以通过返回的句柄读取它。您只需在循环中调用其getSubroutineProducer方法,并在作业全部启动后抓取句柄进行处理:

use IO::Select;
use IO::Pipe::Producer;

my $obj = new IO::Pipe::Producer();
my $sel = new IO::Select;

my $pingHandle = {};
my $pingResult = {};

foreach my $ip (@ips) {
    my $handle = $obj->getSubroutineProducer(\&ping,$ip);
    $sel->add($handle);
    $pingHandle->{$handle} = $ip;
}

上面启动ping作业,然后使用IO :: Select对象以非阻塞方式从中读取它们,直到不再有任何句柄输出为止。您可以通过超时改进以下内容,但我相信ping命令已经有一个......

while(my @fhs = $sel->can_read())
  {
    foreach my $fh (@fhs)
      {
        my $line = <$fh>;
        unless(defined($line))
          {
            $sel->remove($fh);
            close($fh);
            next;
          }
        $pingResult->{$pingHandle->{$fh}} .= $line;
      }
  }

注意,我使用了IP查找句柄,以便能够将句柄的输出放在pingResult哈希中的正确位置。然后你要做的就是打印下面的结果。注意,我删除了&#34; \ n&#34;因为我编辑了你的ping子来打印结果和&#34; \ n&#34;这可以防止潜在的缓冲问题。您可以设置$ |到脚本顶部的非零值,以强制缓冲区每次刷新。

foreach my $ip (keys %$pingResult) {
    print "ping result for $ip: $pingResult->{$ip}";
}


sub ping {
    my $ip = $_[0];
    $mypingresult = `/sbin/ping -t 1 -c 1 $ip | grep packet`;
    print($mypingresult);
}