The Perl programming language and concurrent processing with IPC

So in the last week or so during my annual holiday shutdown I decided to play around with some of my scanning software I wrote sometime ago. I decided to improve it by reducing the number of times forking is required to gain access to those additional cores on my PeeCee.

Forking in perl is trivially easy, however it can get a bit tricky in the management side of things. To handles this better I wrote a nifty little module called Proc::Fork::Control

With this module you can quickly generate an IPC ready multi-core perl instance. Below is a simple and commented example of how this is done:

#!/usr/bin/perl

# Load up some sanity libraries
use warnings;
use strict;

# We want to be able to lock the output handle in the pipe, so import flock
# constants (LOCK_EX / LOCK_UN)
use Fcntl ':flock';

# Use Proc::Fork::Control to simplify management of forks
use Proc::Fork::Control;

# Setup a handle to capture ctrl+c
$SIG{INT} = sub {
        # Is this a child process?
        if(cfork_is_child()){
                print "PID $$ Received shutdown request from parent\n";
        } else {
                # Just the parent exiting.
                print "Bye!\n";
                cfork_kill_children();
        }
        cfork_exit();
};

# Allow up to 2 concurrent forks
cfork_init(2);

# Create two anonymous file handles to link in a pipe
pipe(my $out, my $in);

# Set the forking library to non-blocking so concurrency can happen
cfork_nonblocking(1);

# First fork
my $pid1 = cfork(sub {
        # Just loop endlessly
        while(1){
                # Lock the input file handle for writing
                flock($in, &LOCK_EX);

                # Write the current PID and localtime to the file handle
                print $in "$$ " . localtime() . "\n";

                # Unlock the file handle
                flock($in, &LOCK_UN);

                # fork-safe sleep for 1 second
                cfork_sleep(1);
        }
});

# Second fork
my $pid2 = cfork(sub {
        # Just loop endlessly
        while(1){
                # Lock the input file handle for writing
                flock($in, &LOCK_EX);

                # Write the current PID and localtime to the file handle
                print $in "$$ " . localtime() . "\n";

                # Unlock the file handle
                flock($in, &LOCK_UN);

                # fork-safe sleep for 1 second
                cfork_sleep(1);
        }
});

# Now both forks should be running
# Shut off non blocking mode
cfork_nonblocking(0);

# Main program, print what we find:
print "Waiting for data from $pid1 and $pid2\n";
while(1){
        # Read the output side of the pipe which should contain our data.
        while(<$out>){
                print $_;
        }

        # Sleep for a few seconds between reads
        cfork_sleep(4);
}

When you run this, the output should look something like this:

$ perl ipc.pl  
Waiting for data from 74492 and 74493
74492 Tue Dec 29 10:00:12 2020
74493 Tue Dec 29 10:00:12 2020
74492 Tue Dec 29 10:00:13 2020
74493 Tue Dec 29 10:00:13 2020
74492 Tue Dec 29 10:00:14 2020
74493 Tue Dec 29 10:00:14 2020
74492 Tue Dec 29 10:00:15 2020
74493 Tue Dec 29 10:00:15 2020
74492 Tue Dec 29 10:00:16 2020
74493 Tue Dec 29 10:00:16 2020
^CBye!
PID 74492 Received shutdown request from parent
PID 74493 Received shutdown request from parent

I hope this helps anyone looking to run forks with IPC channels in the future!

Leave a Reply

Your email address will not be published. Required fields are marked *