Perl:异步执行 10 个系统进程

Posted

技术标签:

【中文标题】Perl:异步执行 10 个系统进程【英文标题】:Perl: execute 10 system processes async 【发布时间】:2019-02-18 22:10:23 【问题描述】:

这个问题是针对在 windows 2012 服务器上运行 perl 的。

所以我有一个名为 Commands_To_Run 的文件夹,下面有 100 个批处理文件,例如

Commands_To_Run
 - run_1.bat
 - run_2.bat
 - run_3.bat 
...
 - run_100.bat

这些 run*.bat 文件中的每一个都需要大约 30 分钟才能完成。如果我使用 FOR 循环连续运行这些批处理文件,那么我需要 100 * 30 分钟才能运行。 (太长了!)

我想要的是编写一个 perl 脚本,它一次会执行 10 个批处理文件。一旦任何一个批处理文件完成,下一个批处理文件就会被执行。

例如我想通过run10.bat执行run1.bat。假设 run7.bat 完成然后我想运行下一个 run11.bat 等等。因此,在任何给定时间都有 10 个文件在运行。

我曾想过使用这个 perl 脚本来运行批处理文件,但这会同时运行所有 100 个文件,并且会杀死我的 windows CPU 和处理。

for ($x=0; $x < scalar(@files); $x++ ) 
    $file=@files[$x];
    chomp $file;
    $cmd="start $file ";
    print "Runnung Command is: $cmd\n";
    system($cmd);

我查看了给出的建议,但没有关于如何使用 Forks::Super 的工作示例

【问题讨论】:

perl process queue的可能重复 你需要 GNU Parallel @pilcrow 我查看了给出的建议,但没有关于如何使用 Forks::Super 的工作示例。在我的情况下它将如何工作?没看懂 作为一个不相关的注释,您的 for 循环示例可以写得更糟糕:foreach my $file (@files) ...(注意 foreach 和 for 实际上是同义词) 【参考方案1】:

在队列中并行运行进程的简单方法是使用Parallel::ForkManager

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

use Parallel::ForkManager;    

my $pm = Parallel::ForkManager->new(10); 

# Prepare the list of your batch files (better get names from disk)
my @batch_files = map  "Commands_To_Run/run_$_.bat"  1..100;

foreach my $batch_file (@batch_files)

    $pm->start and next;
    # Run batch job
    say "Running: $batch_file";
    #system($batch_file);        # uncomment to actually run the jobs
    $pm->finish;

$pm->wait_all_children;

这是一个最小但有效的脚本。例如,请参阅 this post 和 this post,了解有关作业如何进行的更多信息,尤其是有关如何从作业返回数据的信息。

注意:这不是核心模块,因此您可能需要安装它

【讨论】:

良好的工作脚本 - 为了完整性添加系统($batch_file);在运行行之后,它将实际执行我的 run_*.bat 文件 @SamB 感谢您的编辑,我已经根据您的意思对它们进行了一些调整。我像往常一样注释掉了实际运行工作的行;我喜欢让人们在启用实际批量运行之前检查事情!另一个流畅的部分是文件夹与脚本等相关的位置,但您可以调整它。 这里需要提到一件事,它等待所有孩子,即只有在前 10 个批次中的每个文件都处理完后,它才会处理下一个 10 个。抱歉,我太早接受了答案。 @SamB 嗯,不,它没有——wait_all_childern 只告诉它“收割”所有子进程完成后(因为所有 fork 的代码都必须; 在 Windows 上它可能会转到join 线程)。当它工作时,它会在一个退出时立即启动一个新进程;它始终保持在 10。(仅打印时看不到,因为它太快了!) 让我更彻底地测试一下。不过,感谢您的出色回答。【参考方案2】:

fmap_scalar function from Future::Utils 可以处理保持一定数量进程运行的所有逻辑,IO::Async::Process 可以异步运行和管理每个进程(鉴于它是 windows,我不确定所有这些是否会合理地工作):

use strict;
use warnings;
use IO::Async::Loop;
use Future::Utils 'fmap_scalar';

my @commands = ...;

my $loop = IO::Async::Loop->new;

my $f = fmap_scalar 
  my $cmd = shift;
  my $f = $loop->new_future;
  $loop->open_process(command => $cmd, on_finish => sub  $f->done($_[1]) );
  return $f;
 foreach => \@commands, concurrent => 10;

my @exit_codes = $f->get; # starts the loop until all processes are done

【讨论】:

我安装了草莓,这就是我得到的——在@INC 中找不到 IO/Async/Loop.pm(您可能需要安装 IO::Async::Loop 模块) IO::Async 和 Future::Utils 都是非核心模块,需要安装。【参考方案3】:

Parallel::ForkManager 依赖于fork,这是 Unix 系统的一个特性,在 Windows 系统上由 Perl(使用线程)严重模拟。我建议直接使用线程。少这样会出错。

use threads; 
use Thread::Queue 3.01 qw( );

sub worker 
   my ($command) = @_;
   system($command);



   my $q = Thread::Queue->new();
   for (1..10) 
      async 
         while (my $job = $q->dequeue()) 
            worker($job);
         
      
   

   $q->enqueue($_) for @commands;
   $q->end();
   $_->join for threads->list;

【讨论】:

【参考方案4】:

只是把它放在那里,但也可以使用批处理文件执行此操作,这应该循环遍历所有 .bat 文件,检查进程计数,如果进程不小于或等于则只启动新的9(如果等于 9,它仍然会踢一):

@echo off
setlocal enabledelayedexpansion
set cnt=1
for %%i in (*.bat) do (
    set id=%%i
    call :check
)

:check
for /f "tokens=1,*" %%a in ('tasklist /FI "WINDOWTITLE eq _process*" ^| find /I /C "cmd.exe"') do set procs=%%a
    if !procs! leq 9 (
    if not "!id!"=="%0" start "_process!cnt!" !id!
    set /a cnt+=1
   ) else (
     goto check
 )

【讨论】:

以上是关于Perl:异步执行 10 个系统进程的主要内容,如果未能解决你的问题,请参考以下文章

linux 系统下oracle 10G perl进程cpu占用100% ,这个进程有啥用?能关掉吗?会不会有啥影响?

如何在 linux 中将 Perl 脚本作为系统守护进程运行?

(王道408考研操作系统)第二章进程管理-第三节1:进程同步

Python 10:线程进程协程异步io

带有 IPC::Run 的 Perl 命令执行器

Perl ipc 使用管道从另一个进程启动一个进程的执行