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

Hello,

I am trying to fork 4 children and pause until they have all completed their task and exited. Then I want to continue with the main script and fork off 4 more children wait until they are finished and then proceed with the script. However, I cannot get the main script to wait until all children have exited--so the script forks off 4 children and then 4 more and then continues running the rest of the script. The dead children become zombies and the whole thing crashes.

How do I reap my children as they exit AND wait until all 4 have exited before I continue with the script??

I have tried to continue only if the exit status ($?) is -1, and installed a signal handler, but it does not seem to be working...

any suggestions?

sub subroutine { $SIG{CHLD} = sub { wait } foreach (1..4) { unless ( $pid = fork ) { # do something here } exit(0); } } until($? == '-1') { print "Waiting... Status is: $?\n" } }

Replies are listed 'Best First'.
(tye)Re: fork n wait
by tye (Sage) on Dec 13, 2000 at 23:55 UTC

    Signal handlers in Perl are always a race condition and will probably cause problems after the handler is called enough times. So it is better to ignore SIGCHLD and wait for the children in a different way.

    Even if that weren't the case, your signal handler could be called once when two children exit at nearly the same time and you'd end up only waiting for one of those two children.

    Also you don't detect when fork() fails.

    Here is a stab at it:

    sub subroutine { foreach (1..4) { my $pid= fork; die "Can't fork: $!\n" unless defined $pid; if( ! $pid ) { # do something here exit(0); } } foreach (1..4) { wait(); print "$_ child finished; status is: $?\n"; } }
    Updated so that is should work now. Here is the full version I used for testing:
    #!/s/t/tye/bin/perl -w use strict; subroutine( @ARGV ); exit( 0 ); sub subroutine { local( $| )= 1; foreach (1..4) { my $pid= fork; die "Can't fork: $!\n" unless defined $pid; if( ! $pid ) { sleep( $_[$_-1] ); exit(0); } else { print "Started child $pid...\n"; } } foreach (1..4) { my $pid= wait(); print "$_ child ($pid) finished; status is: $?\n"; } } # Usage: fork4 6 4 8 2
    Hope that helps.

            - tye (but my friends call me "Tye")
Re (tilly) 1: fork n wait
by tilly (Archbishop) on Dec 13, 2000 at 10:44 UTC
    Take a look at Run commands in parallel for working code for a similar task. Also note that avoiding signal handlers is probably a Good Idea in Perl.
Re: fork n wait
by merlyn (Sage) on Dec 13, 2000 at 23:38 UTC
    Well, amongst the other minor errors, the most important thing I need to know is, are you indeed exiting after the child task, or are you letting a child roll forward into parent-only code? Put an "exit" or "die" at the end of your inner block.

    -- Randal L. Schwartz, Perl hacker

Re: fork n wait
by turnstep (Parson) on Dec 14, 2000 at 02:57 UTC
    I have a similar script that keeps a fixed number of children running at all times. Basically, you just set a variable for the number of kids out, then have the main script decrement that by one when a child exits (however you do it - I use signals, but see the caveats above). In your case, you need to have some code like this:
    ## Not real code! Very poor pseudo-code actually. :) $kids = 4; $kidsOUT=0; $SIG{CHLD} = \&ReapKids; for (1..$kids) { FORK or die if (child) { Do child things exit } else { $kidsOUT++; } } sub ReapKids { ## This detects when the kids are finished, somehow ## If signalling, verify the PID as well if (KidHasDied) { $KidsOUT--; } } { ## Second loop, runs when all kids from first are finished if ($KidsOUT) { ## Still waiting! sleep 10; redo; } } ## Same as above...spawn however many kids you need...
    This is vey, very, rough code but should illustrate one way to do it.