为什么我的Promise(起始块)中的所有Shell进程都不运行?(这是一个错误吗?)

Chr*_*oms 9 perl6 raku

我想运行多个Shell进程,但是当我尝试运行63个以上的进程时,它们将挂起。当我max_threads将线程池减少n为时,运行nth shell命令后它将挂起。

正如您在下面的代码中看到的那样,问题不在于start块本身,而是在start包含shell命令的块中:

#!/bin/env perl6
my $*SCHEDULER = ThreadPoolScheduler.new( max_threads => 2 );

my @processes;

# The Promises generated by this loop work as expected when awaited
for @*ARGS -> $item {
    @processes.append(
        start { say "Planning on processing $item" }
    );
}

# The nth Promise generated by the following loop hangs when awaited (where n = max_thread)
for @*ARGS -> $item {
    @processes.append(
        start { shell "echo 'processing $item'" }
    );
}
await(@processes);
Run Code Online (Sandbox Code Playgroud)

运行./process_items foo bar baz给出以下输出,挂在after之后processing bar,该输出恰好在使用线程nth(here 2nd)运行之后shell

Planning on processing foo
Planning on processing bar
Planning on processing baz
processing foo
processing bar
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?还是这是一个错误?

在CentOS 7上测试的Perl 6发行版:
  Rakudo Star 2018.06
  Rakudo Star 2018.10
  Rakudo Star 2019.03-RC2
  Rakudo Star 2019.03

与Rakudo Star 2019.03-RC2 use v6.c相比,use v6.d没有任何区别。

Jon*_*ton 10

shellrun潜艇使用Proc,这是在以下方面实现的Proc::Async。这在内部使用线程池。通过使用对的阻塞调用来填充池shell,线程池将耗尽,因此无法处理事件,从而导致挂起。

Proc::Async直接将其用于此任务会更好。使用shell和加载实际线程的方法无法很好地扩展;每个OS线程都有内存开销,GC开销等。由于产生一堆子进程不受CPU限制,因此这很浪费。实际上,仅需要一个或两个实际线程。因此,在这种情况下,也许当执行效率低下的事情时,实现就不再给您带来麻烦了。

我注意到使用shell和线程池的原因之一是尝试限制并发进程的数量。但这不是一个非常可靠的方法。仅仅因为当前的线程池实现将默认的最大线程数设置为64,并不意味着它总是会这样做。

这是一个并行测试运行器的示例,该运行器一次最多运行4个进程,收集其输出,并将其封装。它比您可能需要的多一点,但很好地说明了整体解决方案的形状:

my $degree = 4;
my @tests = dir('t').grep(/\.t$/);
react {
    sub run-one {
        my $test = @tests.shift // return;
        my $proc = Proc::Async.new('perl6', '-Ilib', $test);
        my @output = "FILE: $test";
        whenever $proc.stdout.lines {
            push @output, "OUT: $_";
        }
        whenever $proc.stderr.lines {
            push @output, "ERR: $_";
        }
        my $finished = $proc.start;
        whenever $finished {
            push @output, "EXIT: {.exitcode}";
            say @output.join("\n");
            run-one();
        }
    }
    run-one for 1..$degree;
}
Run Code Online (Sandbox Code Playgroud)

这里的关键是调用run-one一个进程何时结束,这意味着您总是用一个新进程替换一个退出的进程,只要有事情要做,就可以同时维护多达4个进程。react由于预订的事件数降至零,因此该块自然会在所有进程完成后结束。