Как дождаться завершения запущенного процесса в Perl, если запущенный процесс не является дочерним процессом?

Я просматриваю сценарий Perl, который использует waitpid($pid, 0) для ожидания завершения текущего процесса. Но оператор print, написанный сразу после этого waitpid, печатает его до завершения процесса.

Я хочу знать, почему waitpid не ждет завершения процесса в первую очередь.

Кроме того, управление запущенным процессом находится в другом модуле, а не в этом Perl-скрипте. Доступен только pid и имя процесса. Я не могу ничего изменить в модуле, который вызывает процесс.


person user3395103    schedule 17.05.2016    source источник
comment
Вы уверены, что $pid это то, что вы думаете? Каково возвращаемое значение waitpid? Он укажет, какой процесс завершился.   -  person Schwern    schedule 17.05.2016
comment
@Schwern - возвращаемое значение waitpid равно -1. Это означает, что такого дочернего процесса, я думаю, нет. И я добавил строки ниже, чтобы проверить, работает $pid или нет. мой $exists = kill 0, $pid; print Процесс запущен\n if ( $exists ); И он печатает заявление   -  person user3395103    schedule 17.05.2016
comment
Тебе нельзя. Вы можете только wait в дочернем процессе. См. perldoc wait (или waitpid, это одно и то же), первое предложение. Если вы хотите знать, когда завершается внешняя программа, вам нужно делать совершенно другие вещи.   -  person zdim    schedule 17.05.2016
comment
@zdim, можете ли вы предложить какой-либо другой подход для достижения этой цели? Я попробовал это - мой $ существует; do { $exists = kill 0, $dgraphpid; } пока ( $ существует );   -  person user3395103    schedule 17.05.2016
comment
Что за внешняя программа? Есть ли у вас какой-либо контроль над ним, как он запускается, что он делает ...? (Это тоже ваш код?)   -  person zdim    schedule 17.05.2016
comment
Внешняя программа @zdim здесь — это другой модуль, который сохраняет текущий pid в файл. Я могу получить доступ к pid и могу использовать его, чтобы дождаться его завершения. То, что я пробовал, я упомянул в комментарии выше.   -  person user3395103    schedule 17.05.2016
comment
Выкладываю кое-что для Linux, с примечанием для Windows. Пожалуйста, дайте мне знать, если для Win нужно больше.   -  person zdim    schedule 18.05.2016


Ответы (2)


Примечание Простой однострочник с kill 0, $pid в конце прокомментирован.


Нам нужно обнаружить завершение внешней программы, которая не была запущена этим скриптом. Вопрос касается использования waitpid. Чтобы скопировать мой ранний комментарий:

Тебе нельзя. Вы можете ждать только дочерний процесс. См. perldoc wait (или waitpid, то же самое), первое предложение.

wait и waitpid ждут сигналов, доставленных сценарию относительно судьбы его потомка (детей). Скрипту незачем получать такие сигналы о процессах, которые он не запускал.


Мы знаем идентификатор процесса и его имя. Его PID можно использовать для опроса, работает ли он. Использование pid само по себе не совсем надежно, так как между нашими проверками процесс может завершиться и случайному новому присвоить тот же самый pid. Мы можем использовать название программы, чтобы усилить это.

В системе Linux информацию о процессе можно получить, используя (множество) ps опций. Любой из них возвращает полный вызов программы

ps --no-headers -o cmd PID
ps --no-headers -p PID -o cmd

Возвращаемая строка может начинаться с пути к интерпретатору (например, для Perl-скрипта), за которым следует полное имя программы. Версия ps -p PID -o comm= возвращает только имя программы, но я обнаружил, что это слово может разбиваться на дефис (если он есть), что приводит к неполному имени. Это может потребовать настройки на некоторых системах, обратитесь к man ps. Если нет процесса с заданным PID, мы ничего не получаем.

Затем мы можем проверить PID и, если он найден, проверить, соответствует ли имя этого PID программе. Имя программы известно, и мы могли бы просто жестко запрограммировать его. Тем не менее, он по-прежнему получается сценарием при запуске с помощью приведенной выше команды ps, чтобы избежать двусмысленностей. (Тогда оно также находится в том же формате для последующего сравнения.) Это само по себе проверяется на известное имя, поскольку нет гарантии, что PID во время выполнения сценария действительно соответствует ожидаемой программе.

use warnings;
use strict;

# For testing. Retrieve your PID as appropriate for real use    
my $ext_pid = $ARGV[0] || $$;

my $cmd_get_name = "ps --no-headers -o cmd $ext_pid";

# For testing.  Replace 'sleep' by your program name for real use
my $known_prog_name = 'sleep';

# Get the name of the program with PID
my $prog_name = qx($cmd_get_name);

# Test against the known name, exit if there is a mismatch
if ($prog_name !~ $known_prog_name) {
    warn "Mismatch between:\n$prog_name\n$known_prog_name -- $!";
    exit;
}   

my $name;
while ( $name = qx($cmd_get_name) and $name =~ /$prog_name/ )
{
    print "Sleeping 1 sec ... \n";
    sleep 1;
}   
# regex above may need slight adjustment, depending on format of ps return

Вывод команды, полученный с помощью qx() выше (оператор обратной кавычки), содержит новую строку. Если это окажется проблемой в том, что делает сценарий, он может быть chomp-ed, что потребует небольшой корректировки. Оставшаяся лазейка в том, что эта та самая программа могла завершиться и перезапуститься между проверками и с тем же PID.

Это будет проверено запуском в оболочке

sleep 30 &
script.pl `ps aux | egrep '[s]leep'`

egrep это grep -E. Вывод из `ps ...` содержит несколько слов. Они передаются в качестве аргументов командной строки нашему сценарию, который использует первый в качестве PID. Если есть проблема с этим, сначала запустите фильтрацию ps, а затем вручную введите PID в качестве входного аргумента скрипта. sleep из 30 секунд, указанных выше, дают достаточно времени, чтобы сделать все это в командной строке.

Код можно упростить, сопоставив $name с жестко заданным $prog_name, если имя программы достаточно уникально и не изменится. Жестко запрограммированное имя используется выше, но для проверки и выдает предупреждение в случае несоответствия. (Если мы полагаемся на него только в жестком коде, мы не можем выдавать предупреждения, если он не соответствует, поскольку тогда это является частью работы кода.)


Если процесс принадлежит тому же пользователю, что и скрипт, можно использовать kill 0, $pid, т.к.

while ( kill 0, $ext_pid ) { sleep 1 }

Тогда вам придется либо сделать еще один вызов, чтобы проверить имя, либо довольствоваться (небольшой) возможностью ошибки в том, какой фактический процесс представляет $pid.

Для большей части этого можно использовать модуль Proc::ProcessTable.

person zdim    schedule 17.05.2016
comment
@user3395103 user3395103 Рад, что это сработало. Получился длинный пост, дайте мне знать, если возникнут дополнительные вопросы или проблемы. - person zdim; 24.05.2016

В waitpid документации говорится:

Ожидает завершения определенного дочернего процесса и возвращает pid умершего процесса или -1, если такого дочернего процесса нет.

Быстрый тест:

my $pid = open my $fh,"-|","sleep 3";
print waitpid(28779,0); # Some other process
print waitpid($pid,0);

28779 — это еще один запущенный в данный момент процесс (взял случайный из ps axu). Вывод:

-1
4088

Вы не можете использовать waitpid для ожидания процесса, который не является дочерним по отношению к вашему текущему процессу.

Команда kill может проверить, запущен ли в данный момент PID:

print kill(0,28779);

Вывод:

1

Вам все равно нужно опросить (цикл со сном), чтобы pid исчез. Также помните, что отслеживаемый процесс может завершиться, а новый может повторно использовать тот же PID перед вашей следующей проверкой (маловероятно, но возможно).

person Sebastian    schedule 17.05.2016