CircusGimp has asked for the wisdom of the Perl Monks concerning the following question:

Hi codemonks,

just a, maybe boring, question to you. Is this a nice way to use fork and pipe, or should i change it?

The Task is to "ping" to a list of IP addresses, to check as fast as possible is the host alive or not. So i should do it parallel, i think. And I don't like to use for such a small job any external fancy IPC Cpan Module i like to use Perl internals, also for the sake of performance only 4 Child's parallel.

use strict; use warnings; my @nums; my @pids; my $max = 4; my $children = 0; foreach my $i ( 0 .. 10 ) { # for all IP's now just 0 to 10 pipe( READER, WRITE ); my $pid; if ( $children == $max ) { $pid = wait(); $children--; } if ( defined( $pid = fork() ) ) { if ($pid) { $children++; print "Parent: forked child ($pid)\n"; push @pids, $pid; close(WRITE); my $num = <READER>; push @nums, ($num); close(READER); wait; } else { my $calc = 365 * $i; # just a example # on this place i will call the function, and capture the # return message print WRITE $calc; close(WRITE); exit; } } else { print "Error: failed to fork\n"; exit; } } for my $pid (@pids) { waitpid $pid, 0; } print "Numbers: @nums \n"; # on this place i will check the array of the return values
This will be my first use of fork, and i use pipe for the return of the values. If there exists other way's, more clean and/or elegant. I would prefer to hear it.

Replies are listed 'Best First'.
Re: Correct usage of fork and pipe?
by kennethk (Abbot) on Aug 07, 2014 at 15:41 UTC
    There are a couple issues with your posted code, with the biggest being that you've actually written blocking code rather than allowing the children to operate in parallel. You're also trying to reap children twice, which will, again, be problematic. Assuming you want to roll your own (good for learning, less good for production), you'll want something closer to:
    use strict; use warnings; my @ips = (1 .. 10); my %pipe; for my $ip (@ips) { pipe my $reader, my $writer; my $pid = fork; die "Error: failed to fork\n" if not defined $pid; if ($pid) { close $writer; $pipe{$ip} = $reader; } else { sleep $ip; print $writer 365*$ip; exit; } } my @nums; for my $ip (@ips) { my $handle = $pipe{$ip}; # Necessary to avoid glob mapping push @nums, <$handle>; } 1 while (wait != -1); print "Numbers: @nums \n";

    Note that since reading is a blocking operation, it can't happen in the normal flow. You also need to wait to reap until after the output channels have been read. Unfortunately, I've missed spec with the code above, because I've removed the maximum number of children constraint. This one is problematic, since it requires that you be able to poll your children to find out if anyone is ready to output and be reaped. You can't just call wait, since your children won't be ready for reaping until they are done with their I/O.

    At this point, you have two choices. You can use flock and have each child write asynchronously to an output channel. This choice is nice, because then you can do your reaping in a manner similar to how you were working before:

    if ( $children == $max ) { wait(); }

    Alternatively, you can use IO::Select (CORE, though select works if you are self-destructive) to check your handles if they are ready to be read, and reap as you go. You could also implement something simpler here with threads, allowing you to bypass the need for pipes.


    #11929 First ask yourself `How would I do this without a computer?' Then have the computer do it the same way.

Re: Correct usage of fork and pipe?
by salva (Canon) on Aug 07, 2014 at 14:20 UTC
    Instead of doing it yourself, you should consider using nmap.
Re: Correct usage of fork and pipe?
by Random_Walk (Prior) on Aug 08, 2014 at 07:56 UTC

    You can also let Net::Ping do it for you...

    # Like tcp protocol, but with many hosts $p = Net::Ping->new("syn"); $p->port_number(getservbyname("http", "tcp")); foreach $host (@host_array) { $p->ping($host); } while (($host,$rtt,$ip) = $p->ack) { print "HOST: $host [$ip] ACKed in $rtt seconds.\n"; }

    Cheers,
    R.

    Pereant, qui ante nos nostra dixerunt!