Почему не все процессы оболочки в моих обещаниях (стартовых блоках) выполняются?(Это ошибка?) - PullRequest
8 голосов
/ 20 марта 2019

Я хочу запустить несколько процессов оболочки, но когда я пытаюсь запустить более 63, они зависают.Когда я уменьшаю max_threads в пуле потоков до n, он зависает после выполнения команды оболочки n th .

Как видно из приведенного ниже кода, проблемане в 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 th (здесь 2 и ) запущен с использованием 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 против use v6.d не имеет значения.

1 Ответ

9 голосов
/ 15 апреля 2019

В подпрограммах 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 естественным образом заканчивается, когда все процессы завершены, из-за того, что количество подписанных событий падает до нуля.

...