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

时间:2019-03-20 16:01:04

标签: perl6

我想运行多个Shell进程,但是当我尝试运行63个以上的进程时,它们将挂起。当我将线程池中的max_threads减少到n时,它在运行n th 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);

运行./process_items foo bar baz给出以下输出,挂在processing bar th 之后的n之后(此处为2 nd )线程已使用shell运行:

Planning on processing foo
Planning on processing bar
Planning on processing baz
processing foo
processing bar

我在做什么错?还是这是一个错误?

在CentOS 7上测试过的Perl 6发行版:
乐天之星2018.06
乐天之星2018.10
乐天之星2019.03-RC2
乐天之星2019.03

使用Rakudo Star 2019.03-RC2,use v6.cuse v6.d没什么区别。

1 个答案:

答案 0 :(得分:9)

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-one,这意味着您总是用一个新进程替换一个退出的进程,只要有事情要做,就最多维护4个进程一次运行。由于预订的事件数降至零,react块自然结束了所有进程。