【问题标题】:How to pass hash contents of a forked subroutine back to main program?如何将分叉子程序的哈希内容传递回主程序?
【发布时间】:2020-09-24 23:18:18
【问题描述】:

我需要在主程序中访问通过分叉的子程序生成的哈希内容。这就是我想要做的事情:-

use Benchmark;
use File::Find;
use File::Basename;
use File::Path;
use Data::Dumper;
use strict;
use warnings;

print "Process ID: $$ \n";
my @PartitionRoots = qw(/nfs/dir1 /nfs/dir2 /nfs/dir3 /nfs/dir4);
my @PatternsToCheck = qw(prefix1 prefix2);
my @MatchedDirnames = qw();
my $DirCount = 0;
my $Forks = 0;
my @AllDirs = qw();
my %SweepStats = ();

foreach my $RootPath (@PartitionRoots) {
    foreach my $Pattern (@PatternsToCheck) {
        if (grep {-e} glob ("$RootPath/$Pattern*")) {
            my @Files = glob ("$RootPath/$Pattern*");
            foreach my $FileName (@Files) {
                if (-d $FileName) {
                    $DirCount++;
                    push (@AllDirs, $FileName);
                    my $PID = fork;
                    if (not defined $PID) {
                        warn 'Could not fork!\n';
                        next;
                    }
                    if ($PID) {
                        $Forks++;
                        print "In the parent PID ($$), Child pid: $PID Number of forked child processes: $Forks\n";
                    } else {
                        print "In the child PID ($$)\n";
                        find(\&file_stats, $FileName);
                        print "Child ($$) exiting...\n";
                        exit;
                    }
                }
            }
        }
    }
}
for (1 .. $Forks) {
   my $PID = wait();
   print "Parent saw child $PID exit.\n";
}
print "Parent ($$) ending.\n";

print Dumper (\%SweepStats);
foreach my $DirName (@AllDirs) {
    print ("Printing $DirName contents...\n");
    foreach (@{$SweepStats{$DirName}}) {
        my $uname = $_->{uname};
        my $mtime = $_->{mtime};
        my $size = $_->{size};
        my $file = $_->{file};
        print ("$uname $mtime $size $file\n");
    }
}

sub file_stats {
    if (-f $File::Find::name) {
        my $FileName = $_;
        my $PathName = dirname($_);
        my $DirName = basename($_);     
        my $uid = (stat($_))[4];
        my $uname = getpwuid($uid);
        my $size = (stat($_))[7];
        my $mtime = (stat($_))[9];
        if (defined $uname && $uname ne '') {
            push @{$SweepStats{$FileName}}, {path=>$PathName,dir=>$DirName,uname=>$uname,mtime=>$mtime,size=>$size,file=>$File::Find::name};
        } else {
            push @{$SweepStats{$FileName}}, {path=>$PathName,dir=>$DirName,uname=>$uid,mtime=>$mtime,size=>$size,file=>$File::Find::name};
        }
    }
    return;
}

exit;

...但是 Dumper 是空的,所以紧随其后的取消引用和打印也是空的。我知道文件统计收集正在工作,因为如果我用打印语句替换“push @{$SweepStats{$FileName}}”语句,我就会看到预期的结果。我只需要从全局级别正确访问哈希,但我不能完全正确。我在这里做错了什么?有各种关于将哈希传递给子程序的帖子,但反之则不然。

谢谢!

【问题讨论】:

  • 如果你想限制模拟子节点的数量,你可以使用 Parallel::ForkManager。它甚至提供了一种将数据返回给父级的方法。

标签: perl multiprocessing fork ipc


【解决方案1】:

fork 调用创建了一个新的独立进程。该子进程及其父进程无法写入彼此的数据。因此,为了在父子节点之间交换数据,我们需要使用一些进程间通信 (IPC) 机制。

到目前为止,使用一个处理细节的库是最简单的,Parallel::ForkManager 在这里似乎相当合适,因为它提供了一种将数据从子级传递回父级的简单方法,并且它有一个简单的队列 (以保持同时处理的数量限制在给定的数量)。

这是一些工作代码,然后是 cmets

use warnings;
use strict;
use feature 'say';

use File::Find;
use File::Spec;
use Parallel::ForkManager;
    
my %file_stats;  # written from callback in run_on_finish()
 
my $pm = Parallel::ForkManager->new(16);

$pm->run_on_finish(
    sub {  # 6th argument is what is passed back from finish()
        my ($pid, $exit, $ident, $signal, $core, $dataref) = @_;
        foreach my $file_name (keys %$dataref) {
            $file_stats{$file_name} = $dataref->{$file_name};
        }
    }   
);

my @PartitionRoots  = '.';  # For my tests: current directory,
my @PatternsToCheck = '';   # no filter (pattern is empty string)

my %stats;  # for use by File::Find in child processes

foreach my $RootPath (@PartitionRoots) {
    foreach my $Pattern (@PatternsToCheck) {
        my @dirs = grep { -d } glob "$RootPath/$Pattern*";
        foreach my $dir (@dirs) { 
            #say "Looking inside $dir";       
            $pm->start and next;          # child process
            find(\&get_file_stats, $dir);
            $pm->finish(0, { %stats });   # exits, {%stats} passed back
        }
    }   
}
$pm->wait_all_children;
    
sub get_file_stats {
    return if not -f;
    #say "\t$File::Find::name";

    my ($uid, $size, $mtime) = (stat)[4,7,9];
    my $uname = getpwuid $uid;

    push @{$stats{$File::Find::name}}, {
        path => $File::Find::dir,
        dir  => ( File::Spec->splitdir($File::Find::dir) )[-1],
        uname => (defined $uname and $uname ne '') ? $uname : $uid,
        mtime => $mtime,
        size => $size,
        file => $File::Find::name
    };
}

评论

  • 所有这一切的主要问题是:在您的三层层次结构的哪个部分产生子进程?我把它留在问题中,每个目录都有一个子目录。如果(大多数)目录有很多文件,这可能是合适的;但如果不是这样,并且每个孩子几乎没有工作要做,那么它可能会变得太忙,开销可能会降低/拒绝加速

  • %stats 哈希是File::Find 存储它找到的数据所必需的,需要在所有循环之外声明,以便在子循环中看到它。所以它被所有子进程继承,但是每个子进程都会得到自己的副本,我们不必担心数据重叠等问题

  • 我还简化(并更正了)除分叉之外的代码,遵循我认为需要的内容。如果关闭,请告诉我

  • 查看链接文档,例如this post 和其中的链接了解详情


为了显示复杂的数据结构,使用一个库,其中有很多。

我使用Data::Dump,旨在简单地打印出来,

use Data::Dump qw(dd pp);
...
dd \%file_stats;  # or: say "Stats for all files: ", pp \%file_stats;

因其紧凑的输出,而使用最广泛的是核心Data::Dumper

use Data::Dumper
...
say Dumper \%file_stats;

它还“理解”数据结构(因此您大部分时间都可以eval 回复它们)。

(注意:在这种情况下,可能会有很多的输出!所以重定向到一个文件,或者在第一次迭代后退出这些循环,以便看看它是如何进行的。)


当一个进程被分叉时,来自父进程的变量和数据可供子进程使用。出于效率原因,它们不会立即被复制,因此最初孩子确实会读取父母的数据。但是在父进程或子进程中fork调用后生成的任何数据都不能被其他进程看到。

【讨论】:

  • @Chris 我可以想象一下这可能有点多,请告诉我进展如何
  • 谢谢@zdim!完全安装 Parallel::ForkManager 及其所有依赖项需要付出一些努力——它是一扇由门组成的门。但它正在工作,你的代码也是如此。我唯一的后续问题是:1)我可以将 ForkManager->new(16) 更改为基于整数的变量,它代表收集的目录数量(当然是在合理范围内)?和 2) 如何在 $pm=>wait_all_children 完成后取消引用并打印 %stats 的内容?
  • @Chris 太好了:)。 (1) new 的参数是它将继续运行(最多)多少个进程。因此,一旦它达到 16,它将不再创建,而是会坐在那里($pm->start)并等到一个进程退出,然后然后它会创建另一个。 “收集的目录数量”......您可以先弄清楚有多少,然后创建一个P::FM 对象(带有new)?或者,在进行时计算目录并将新进程的创建条件设置为该数字? (例如,按照我的帖子中的链接,在这个答案中链接。)
  • @Chris (2) 有许多模块可以打印复杂的数据结构。有些被设计为实际上能够在以后重现有效的 Perl 数据结构,有些则只能很好地显示。添加到帖子中。我提到了两个,但还有一些其他的,以满足任何人的喜好。
  • @Chris "表示收集的目录数 (...)?" --- 你的意思是说,如果你有 36 个目录,你使用 12 个过程,或一些这样的?我会说你不需要那个。如果您可以为这项工作腾出 4 个内核,那么可以使用 new(4),或者可能是 new(8),甚至 16,因为如果您想要每个周期,这对 CPU 来说是轻量级的。 (计时。)如果有 许多 个目录,那么队列正是您想要的,而进程的确切数量不那么重要(取决于您的硬件可以做得很好)。
【解决方案2】:

试试这个模块:IPC::Shareable。 由perldoc perlipc推荐,您可以在这里找到问题的答案。

【讨论】:

  • 谢谢,@k-mx 我尝试了 IPC::Shareable 模块并让它非常接近工作。但是我遇到了可怕的“共享数据长度超过共享段大小”消息,我似乎无法解决。最后,收集的数据量是 50+ MB,所以我显然需要重新考虑整个方法......
猜你喜欢
  • 2020-06-14
  • 1970-01-01
  • 2013-03-11
  • 2011-06-21
  • 2011-02-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-25
相关资源
最近更新 更多