我想运行多个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.c
与use v6.d
没什么区别。
答案 0 :(得分:9)
shell
和run
子项使用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
块自然结束了所有进程。