#perl #semaphore
#perl #семафор
Вопрос:
Мне нужно создать программу, которая одновременно запускала бы 3 процесса в случайной последовательности из списка и блокировала бы эти процессы семафором один за другим, чтобы избежать дубликатов. Например, у вас есть список из 3 программ:
@array = ( 1,2,3);
- perl script.pl сначала выполняется 2;
- Случайным образом пытается снова запустить 2 и получает ошибку (потому что 2 теперь заблокирован с помощью семафора).
- Выполняется 1.
- Выполняется 3.
- 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 startedn";
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;
Проблемы:
- Необходимо заблокировать файл; (Я не знаю, как работать с semaphore. Некоторые попытки заблокировать файлы завершились неудачей, поэтому этот код был исключен.)
- Дочерний элемент ожидает завершения первой программы перед вторым запуском. Как я могу запустить три из программ одновременно с одним дочерним элементом? (Возможно ли это или я должен создать одного дочернего элемента для одной программы?).
- Программы не имеют графического интерфейса пользователя и должны выполняться в терминале. Как запустить программу в новом окне терминала (вкладка)?
- Нет правильной проверки, были ли запущены все программы @programs. — менее важно.
Комментарии:
1. Если вы не используете потоки, вам понадобится отдельный процесс для каждого дочернего элемента. Непонятно, зачем вам нужны семафоры; вы вполне могли бы перетасовать массив (осторожно) и выполнить итерацию по процессам, запуская каждый по очереди (зная, что дубликатов не будет, поскольку их не было в массиве до его перетасовки), а затем просто дождаться, пока все дочерние элементы не умрут. Это вообще не очень сложно.
2. @Jonathan Leffler Если бы я только мог избежать этих семафоров : ( Задача состоит в том, чтобы предотвратить запуск нового процесса с помощью semaphore, если он уже был запущен. У меня возникли сложности с этим, поэтому я попросил помощи. Тем не менее, спасибо.
3. Взгляните на Parallel::ForkManager .
4. Смотрите ответ @Mat, который делает почти точно то, что я описал. Нет необходимости в семафорах; используйте структуру данных, которая устраняет необходимость в них (случайно перетасованный массив).
Ответ №1:
Ваши требования к случайности очень странные, но, если я правильно понял ваши требования, вам не нужна какая-либо блокировка, чтобы делать то, что вы хотите. (Итак, 1)
в вашем вопросе нет)
Начните с перетасовки программного массива, затем запустите каждую команду из этого перетасованного массива (это касается вашего 4)
). Тогда только waitpid
после того, как вы все запустили (что касается вашего 2)
).
Приведенный ниже код делает это, запуская различные sleep
экземпляры в новых терминалах (я использую 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 $pidn";
push @pids, $pid;
}
}
# Wait for them
map {
waitpid($_, 0);
print "$_ done!n";
} (@pids);
Не уверен, что перетасовка является лучшей из существующих, но она работает. Идея, стоящая за этим, заключается в том, чтобы просто выбрать один элемент случайным образом из исходного (отсортированного) списка, удалить его оттуда и добавить в перетасованный. Повторяйте, пока начальный список не опустеет.
Если вы пытаетесь заблокировать программы в масштабах всей системы (т. Е. никакой другой процесс в вашей системе не должен иметь возможности их запускать), то извините, но это невозможно, если программы не защищают себя от параллельного выполнения.
Если ваш вопрос был о семафорах, то мне жаль, что я пропустил вашу точку зрения. В документации IPC есть пример кода для этого. Я действительно не думаю, что необходимо идти на такую сложность для того, что вы пытаетесь сделать.
Вот как вы могли бы это сделать, используя IPC::Semaphore
модуль для удобства.
В начале вашего main создайте набор семафоров с таким количеством семафоров, сколько требуется:
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
должно быть уникальным для каждой программы (например, это может быть индекс в вашем массиве programs). Не используйте exec
для запуска программы. Используйте system
вместо этого, например.
Обратите внимание, что в этом случае вам придется иметь дело с кодом выхода дочернего процесса. Если код завершения равен нулю, вы можете пометить эту программу как запущенную. Если нет, вам нужно повторить попытку. (Это приведет к беспорядку, вам нужно будет отслеживать, какая программа была запущена, а какая нет. Я бы предложил хэш с номером программы ( $proc_number
), где вы бы сохраняли, завершена она уже или нет, и текущий pid, выполняющий (или пытающийся выполнить) этот код. Вы можете использовать этот хэш, чтобы выяснить, какую программу еще нужно выполнить.)
Наконец, после того, как все сделано и вы дождались всех дочерних элементов, вы должны убрать за собой:
$sem->remove;
В этом коде отсутствует надлежащая проверка ошибок, он будет работать странно (т. Е. совсем не хорошо), если очистка была выполнена неправильно (т. Е. семафоры уже лежат где попало при запуске кода). Но это должно помочь вам начать.
Комментарии:
1. Да, это о семафорах. Я знаю, что могу просто обойтись без этого, но моя задача — использовать семафоры, и эти требования к семафорам являются причиной создания этого потока. Спасибо за помощь.