В подпрограммах shell
и run
используется Proc
, что реализовано в терминах Proc::Async
. Это использует внутренний пул потоков. Заполняя пул блокирующими вызовами shell
, пул потоков истощается и не может обрабатывать события, приводящие к зависанию.
Было бы гораздо лучше использовать Proc::Async
непосредственно для этой задачи. Подход с использованием shell
и загрузкой реальных потоков не будет хорошо масштабироваться; каждый поток ОС имеет накладные расходы памяти, GC и так далее. Поскольку порождение группы дочерних процессов не связано с процессором, это довольно расточительно; в действительности нужны только одна или две реальные темы. Так что, в этом случае, возможно, реализация, отталкивающая вас от вас, когда вы делаете что-то неэффективное, не самая плохая вещь.
Я заметил, что одной из причин использования 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
естественным образом заканчивается, когда все процессы завершены, из-за того, что количество подписанных событий падает до нуля.