user934718
user934718

Reputation: 55

How to run in parallel two command from a parent one?

I have two code

1.

use File::Temp qw(tempfile);
$tmp = new File::Temp( UNLINK => 0 );
system("tv_grab_au | tv_sort >> $file");
system("cp $file $HOME/.xmltv/listings.xml");

unlink($file);

2.

while (-e $file) {
sleep 2;
system("tvtime-command DISPLAY_MESSAGE \'Updating TV Guide. Please wait this might take a several minutes...\'");
}

I would like to combine this 2 code to run tv_grab_au xmltv grabber (update TV Guide), and simultaneously, send command to tvtime for display message 'Updating TV Guide. Please wait this might take a several minutes...', every two seconds, until $file exist.

I try this one:

use strict;
use warnings;
use File::Temp qw(tempfile);
my $file = new File::Temp( UNLINK => 0 );
use POSIX qw(:sys_wait_h);
$|++;

defined(my $pid = fork) or die "Couldn't fork: $!";

if (!$pid) {    
    system("tv_grab_huro | tv_sort >> $file");
    unlink($file);
}
else { 
    while (! waitpid($pid, WNOHANG)) {
        system("tvtime-command DISPLAY_MESSAGE \'Updating TV Guide. Please wait this might take a several minutes...\'");
        sleep 2;
        }
}

Thanks.

Upvotes: 2

Views: 2281

Answers (2)

Joe McMahon
Joe McMahon

Reputation: 3392

Use fork(). I've added extra sleep() calls so you can see that the processes both run and work. In practice, the crontab update will probably run fast enough that the monitor loop doesn't run at all, or only runs once. I used "unless(...)" because it seems to me to make the code clearer; the thing to remember is that fork() returns the pid to the parent, and zero to the child. The process that doesn't see the pid is therefore a subprocess. (As has been pointed out, if the fork fails, the fork will return undef, and the code will be executing in the original process. In our case, that will simply mean that the monitoring starts up after the writing finishes, so the only thing we lose is the monitoring.)

my $file = "/tmp/.$$.crontab.txt";
my $crontab = <<EOS;
# Crontab lines here. Inserted at @{[scalar localtime()]}
EOS

my ($writer_pid, $monitor_pid);

$|++;

# Open file BEFORE launching processes. The monitor depends on the file's
# presence or absence, so if we opened it in the writer process, there'd be a
# chance the monitor process would check before we created it, and exit without
# monitoring.
die "Cannot open temp file\n" unless open(WRITE, ">" . $file);

# Crontab file handle gets passed to the forked process, so we can just use it.
# Altered so we can see the process do its thing.
unless ($writer_pid = fork()) {
        print WRITE $crontab."\n";
        close WRITE;
        print("crontab -l |grep -v backup >> $file");
        sleep 20;
        print("crontab $file");
        sleep 10;
        unlink($file);
        print "done!\n";
        exit;
}

# Either file will exist, or the previous process will
# have completed. If it exists, we monitor. If not,
# we exit immediately.
unless ($monitor_pid = fork()) {
    # Child: monitor the writer.
    my $waitcount = 1;
    while ( -e $file ) {
       sleep 2;
       print  "($waitcount) installing crontab...";
       $waitcount++;
    }
    print "installed\n";
    exit;
}

waitpid($monitor_pid, 0);
waitpid($writer_pid,0);
print "both processes done\n";

Upvotes: 0

mob
mob

Reputation: 118665

The builtin fork function creates a copy of your current program in a new background process. The original process and the "child" process will then run at the same time. So you can do something like:

use File::Temp qw(tempfile);
my $file = new File::Temp( UNLINK => 0 );

my $new_pid = fork();
die "fork failed $!" unless defined $new_pid;   # this is uncommon

# Task 1 - in the background
if ($new_pid == 0) {
    system("tv_grab_au | tv_sort >> $file");
    system("cp $file $HOME/.xmltv/listings.xml");    
    unlink($file);
    exit;            # don't forget this part!
}

# Task 2 - in the foreground
while (-e $file) {
    print "...";
    sleep 2;
}

Using $file as an indicator of when the first task has finished has some drawbacks. What if the child code has some runtime error? What if the child process gets interrupted? The child process could exit before it gets a chance to delete $file. Then your while loop in the parent process would never end.

The builtin waitpid command can check if a child process is still running, and can handle the case where the child terminates abnormally.

# Task 2 
use POSIX ':sys_wait_h';
while (! waitpid $new_pid, &WNOHANG) {   # WNOHANG => non-blocking wait
    print "...";
    sleep 2;
}

Upvotes: 4

Related Questions