【问题标题】:Why don't all the shell processes in my promises (start blocks) run? (Is this a bug?)为什么我的 Promise(开始块)中的所有 shell 进程都没有运行? (这是一个错误吗?)
【发布时间】:2019-08-11 09:46:45
【问题描述】:

我想运行多个 shell 进程,但是当我尝试运行超过 63 个时,它们会挂起。当我将线程池中的max_threads 减少到n 时,它会在运行nth shell 命令后挂起。

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

#!/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 之后挂起,就在nth(这里是2nd)线程有使用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 没有任何区别。

【问题讨论】:

  • 启动循环块start {say "Starting $item"; loop (my $i=0; $i<100000; $i++) {state $a++;} } 会运行到池中的最大线程数。如果作业多于线程,则其余的等待空闲线程,然后按预期执行。对于我的两分钱,你的问题突出了一个错误。
  • @drclaw 谢谢!我已经更新了我的问题,试图强调它何时工作(如您所展示)和它看起来“有问题”之间的区别。

标签: raku


【解决方案1】:

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 块自然会在所有进程完成后结束,因为订阅的事件数会降至零。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-01
    • 1970-01-01
    • 2021-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多