Perl, форк, семафоры, процессы - PullRequest
5 голосов
/ 29 мая 2011

Мне нужно создать программу, которая будет запускать 3 процесса одновременно в произвольной последовательности из списка и блокировать эти процессы семафором один за другим, чтобы избежать дублирования. Например, у вас есть список из 3 программ:

@array = ( 1,2,3);
  1. perl script.pl сначала запускается 2;
  2. Случайно пытается снова запустить 2 и получает ошибку (потому что 2 теперь заблокирован семафором).
  3. Прогоны 1.
  4. Прогоны 3.
  5. script.pl ждет завершения всех 1,2,3, а затем завершает работу.

Вот мой код:

#!/usr/bin/perl -w
use IPC::SysV qw(IPC_PRIVATE S_IRUSR S_IWUSR IPC_CREAT);
use IPC::Semaphore;
use Carp ();

print "Program started\n";

sub sem {
    #semaphore lock code here
  }

sub chooseProgram{
    #initialise;
    my $program1 = "./program1.pl";
    my $program2 = "./program2.pl";
    my $program3 = "./program3.pl";
    my $ls = "ls";
    my @programs = ( $ls, $program1, $program2, $program3 );    

    my $random = $programs[int rand($#programs+1)];
    print $random."\n";
    return $random;
}

#parent should fork child;
#child should run random processes;
#avoid process clones with semaphore;
sub main{                   
    my $pid = fork();
    if ($pid){
        #parent here
    }
    elsif (defined($pid)){
        #child here     
        print "$$ Child started:\n";
        #simple cycle to launch and lock programs
        for (my $i = 0; $i<10; $i++){
            # semLock(system(chooseProgram()); #run in new terminal window
            # so launched programs are locked and cannot be launched again
        }
    }
    else {
        die("Cannot fork: $!\n");
    } 
    waitpid($pid, 0);
    my $status = $?;
    #print $status."\n";
}
main();
exit 0;

Проблемы:

  1. Нужно заблокировать файл; (Я не знаю, как работать с семафором. Не удалось несколько попыток заблокировать файлы, поэтому код был исключен.)
  2. Ребенок ждет, пока первая программа не закончится, перед вторым запуском. Как я могу запустить три программы одновременно с одним ребенком? (Возможно ли создать одного ребенка для одной программы?).
  3. Программы не из графического интерфейса и должны запускаться в терминале. Как запустить программу в новом окне терминала (вкладке)?
  4. Нет правильной проверки, все ли программы @programs были запущены. -- менее важно.

1 Ответ

3 голосов
/ 29 мая 2011

Ваше требование к случайности очень странно, но если я правильно понял ваши требования, вам не понадобится никакой блокировки, чтобы делать то, что вы хотите.(Так что 1) в вашем вопросе пропал)

Начните с перетасовки массива программы, затем запустите каждую команду этого перетасованного массива (это касается вашего 4)).Тогда только waitpid после вы запустили все (что касается ваших 2)).

Код, приведенный ниже, делает это, начиная различные экземпляры sleep в новых терминалах (Iиспользуйте urxvt, адаптируйтесь в зависимости от того, какой терминал вы хотите породить - это касается вашего 3)).

#! /usr/bin/perl -w
use strict;
use warnings;

my @progs = ("urxvt -e sleep 5", "urxvt -e sleep 2", "urxvt -e sleep 1");
my @sgrop;
my @pids;

# Shuffle the programs
while (my $cnt = scalar(@progs)) {
    push @sgrop, splice @progs, int(rand($cnt)), 1;
}

# Start the progs
foreach my $prog (@sgrop) {
    my $pid = fork();
    if (!$pid) {
        exec($prog);
        # exec does not return
    } else {
        print "Started '$prog' with pid $pid\n";
        push @pids, $pid;
    }
}

# Wait for them
map {
    waitpid($_, 0);
    print "$_ done!\n";
} (@pids);

Не уверен, что тасование является лучшим, но оно работает.Идея заключается в том, чтобы просто выбрать один элемент случайным образом из исходного (отсортированного) списка, удалить его оттуда и добавить в перемешанный.Повторяйте до тех пор, пока начальный список не станет пустым.

Если вы пытаетесь заблокировать программы в масштабе всей системы (т. Е. Ни один другой процесс в вашей системе не может их запустить), то извините, но это невозможноесли только программы не защищают себя от одновременного выполнения.

Если ваш вопрос касался семафоров, то извините, я упустил вашу мысль.Документация IPC содержит пример кода для этого.Я действительно не думаю, что нужно идти к такой сложности для того, что вы пытаетесь сделать.

Вот как вы могли бы сделать это, используя модуль IPC::Semaphore для удобства.

В начале вашего основного файла создайте набор семафоров с таким количеством семафоров, сколько потребуется:

use IPC::SysV qw(S_IRUSR S_IWUSR IPC_CREAT IPC_NOWAIT);
use IPC::Semaphore;

my $numprocs = scalar(@progs);
my $sem = IPC::Semaphore->new(1234, # this random number is the semaphore key. Use something else
    $numprocs,                      # number of semaphores you want under that key
    S_IRUSR | S_IWUSR | IPC_CREAT);

Проверьте ошибки, затем инициализируйте все семафоры до 1.

$sem->setall( (1) x $numprocs) || die "can't set sems $!";

В коде, который запускает ваши процессы, прежде чем запускать (хотя после разветвления), попробуйте захватить семафор:

if ($sem->op($proc_number, -1, IPC_NOWAIT)) {
  # here, you got the semaphore - so nothing else is running this program
  # run the code
  # and once the code is done:
  $sem->op($proc_number, 1, 0); # release the semaphore
  exit(0);
} else {
  # someone else is running this program already
  exit(1); # or something
}

В приведенном выше примере $proc_number должно быть уникальным для каждогопрограмма (это может быть, например, индекс в массиве программ). Не используйте exec для запуска программы.Вместо этого используйте, например, system.

Обратите внимание, что в этом случае придется обработать код выхода дочернего процесса.Если код выхода равен нулю, вы можете пометить эту программу как запущенную.Если нет, вам нужно повторить попытку.(Это может запутаться, вам нужно будет отслеживать, какая программа была запущена или нет. Я бы предложил хеш с номером программы ($proc_number), где вы бы сохранили, была ли она уже завершена или нет, итекущий pid запускает (или пытается запустить) этот код. Вы можете использовать этот хеш, чтобы выяснить, какую программу еще нужно выполнить.)

Наконец, после того, как все сделано, и вы дождались всех потомков,Вы должны очистить после себя:

$sem->remove;

В этом коде отсутствует правильная проверка ошибок, он будет работать странно (то есть не очень хорошо), если очистка не была выполнена правильно (т.е. семафоры уже лежат, когда кодначинается).Но это должно начать вас.

...