#raku
#raku
Вопрос:
Я хочу запустить несколько процессов оболочки, но когда я пытаюсь запустить более 63, они зависают. Когда я уменьшаю max_threads
в пуле потоков до n
, он зависает после выполнения n
-й команды оболочки.
Как вы можете видеть в приведенном ниже коде, проблема не в 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
, который происходит сразу после того, как n
й (здесь 2
nd) поток запустился с использованием shell
:
Planning on processing foo Planning on processing bar Planning on processing baz processing foo processing bar
Что я делаю не так? Или это ошибка?
Дистрибутивы Perl 6, протестированные на CentOS 7:
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
versus use v6.d
не имело никакого значения.
Комментарии:
1. Запуск блока цикла
start {say "Starting $item"; loop (my $i=0; $i<100000; $i ) {state $a ;} }
запускает максимальное количество потоков в пуле. Если заданий больше, чем потоков, остальные ожидают свободного потока, а затем выполняются, как и ожидалось. На мой взгляд, ваш вопрос указывает на ошибку.2. @drclaw Спасибо! Я обновил свой вопрос, чтобы попытаться подчеркнуть разницу между тем, когда это работает (как вы демонстрируете) и когда это кажется «глючным».
Ответ №1:
Подлодки 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
Блок, естественно, заканчивается, когда все процессы завершены, из-за того, что количество событий, на которые подписаны, падает до нуля.