Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl, fork, semaphores, processes

Tags:

semaphore

perl

I need to create a program that would run 3 processes at the same time in random sequence from a list and lock those processes with semaphore one by one so to avoid duplicates. For example, you have a list of 3 programs:

@array = ( 1,2,3);
  1. perl script.pl runs 2 at first;
  2. By random tries to run 2 again and receives an error (because 2 is now locked with semaphore).
  3. Runs 1.
  4. Runs 3.
  5. script.pl waits all of 1,2,3 to end work and then exit itself.

Here's my code so far:

#!/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;

Problems:

  1. Need to lock file; (I don't know how to work with semaphore. Failed some attempts to lock files so excluded that code.)
  2. Child waits until first program ends before second start. How can I start three of programs at the same time with one child? (Is it possible or should I create one child for one program?).
  3. Programs are non-gui and should run in terminal. How to run a program in new terminal window(tab)?
  4. No correct check if all programs of @programs were launched yet. -- less important.
like image 986
ted Avatar asked May 29 '11 14:05

ted


People also ask

How do you fork a process in Perl?

On some platforms such as Windows where the fork() system call is not available, Perl can be built to emulate fork() at the interpreter level. The fork() function is used to clone a current process. This call create a new process running the same program at the same point.

How to use system() function to execute a Perl script in Unix?

You can also use system () function to execute any Unix command, whose output will go to the output of the perl script. By default, it is the screen, i.e., STDOUT, but you can redirect it to any file by using redirection operator > − When above code is executed, it lists down all the files and directories available in the current directory −

How to access the shared memory region of a semaphore?

(Since a child created by fork (2) inherits its parent's memory mappings, it can also access the semaphore.) Any process that can access the shared memory region can operate on the semaphore using sem_post (3), sem_wait (3), etc.

What are the basic IPC facilities of Perl?

The basic IPC facilities of Perl are built out of the good old Unix signals, named pipes, pipe opens, the Berkeley socket routines, and SysV IPC calls. Each is used in slightly different situations. Perl uses a simple signal handling model: the %SIG hash contains names or references of user-installed signal handlers.


1 Answers

Your randomness requirement is very strange, but if I understood your requirements correctly, you don't need any sort of locking to do what you want. (So 1) in your question is gone)

Start by shuffling the program array, then start each command of that shuffled array (this deals with your 4)). Then only waitpid after you've started everything (which deals with your 2)).

The code below does that, starting various sleep instances in new terminals (I use urxvt, adapt depending on what terminal you want to spawn - this deals with your 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);

Not sure the shuffling is the best out there, but it works. The idea behind it is just to pick one element at random from the initial (sorted) list, remove it from the there and add it to the shuffled one. Repeat until the initial list is empty.

If you're trying to lock the programs system wide (i.e. no other process in your system should be able to start them), then I'm sorry but that's not possible unless the programs protect themselves from concurrent execution.

If your question was about semaphores, then I'm sorry I missed your point. The IPC documentation has sample code for that. I don't really think it's necessary to go to that complexity for what you're trying to do though.

Here's how you could go about it using the IPC::Semaphore module for convenience.

At the start of your main, create a semaphore set with as many semaphores as required:

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);

Check for errors, then initialize all the semaphores to 1.

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

In the code that starts your processes, before you start (after the fork though), try to grab the semaphore:

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
}

In the above, $proc_number must be unique for each program (could be it's index in your programs array for instance). Don't use exec to start the program. Use system instead for example.

Note that you will have to deal with the exit code of the child process in this case. If the exit code is zero, you can mark that program as having run. If not, you need to retry. (This is going to get messy, you'll need to track which program was run or not. I'd suggest a hash with the program number ($proc_number) where you'd store whether it already completed or not, and the current pid running (or trying to run) that code. You can use that hash to figure out what program still needs to be executed.)

Finally after all is done and you've waited for all the children, you should clean up after yourself:

$sem->remove;

This code lacks proper error checking, will work strangely (i.e. not well at all) if the cleanup was not done correctly (i.e. semaphores are already laying around when the code starts). But it should get you started.

like image 188
Mat Avatar answered Nov 15 '22 07:11

Mat