如何在后台执行特定的子程序代码?
我想并行触发下面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});
}
答案 0 :(得分:3)
Parallel::ForkManager
模块提供了从子进程返回到父进程的数据。它的工作原理是将其序列化并写入临时文件
Parallel::ForkManager->new
调用的第二个参数必须是可以存储这些临时文件的目录的路径,而子项finish
调用的第二个参数必须是引用< / em>到应返回的标量值。标量值可以是简单的字符串或数字,或者如果必须提供复杂的结构,它可以是对散列或数据的引用
要收集返回数据,您必须定义一个run_on_finish
回调,用于收集有关终止子进程的信息
在这种情况下,我的ips.txt
文件只包含八个字母A
到H
,我使用了此代码
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);
}