How to make waitpid block the loop

user4035 picture user4035 · Jun 7, 2013 · Viewed 8k times · Source

The following code runs 2 children, who will wait for 10 seconds and terminate. The parent is sitting in a loop, waiting for the children to terminate:

#!/usr/bin/perl

use strict;
use warnings;
use POSIX ":sys_wait_h";

sub func
# {{{
{
      my $i = shift;
      print "$i started\n";
      $| = 1;
      sleep(10);
      print "$i finished\n";
}
# }}}

my $n = 2;
my @children_pids;

for (my $i = 0; $i < $n; $i++) {
      if ((my $pid = fork()) == 0) {
            func($i);
            exit(0);
      } else {
            $children_pids[$i] = $pid;
      }
}

my $stillWaiting;
do {
      $stillWaiting = 0;
      for (my $i = 0; $i < $n; ++$i) {
            if ($children_pids[$i] > 0)
            {
                  if (waitpid($children_pids[$i], WNOHANG) != 0) {
                        # Child is done
                        print "child done\n";
                        $children_pids[$i] = 0;
                  } else {
                        # Still waiting on this child
                        #print "waiting\n";
                        $stillWaiting = 1;
                  }
            }
            #Give up timeslice and prevent hard loop: this may not work on all flavors of Unix
            sleep(0);
      }
} while ($stillWaiting);

print "parent finished\n";

The code is based upon this answer: Multiple fork() Concurrency

It works correctly, but the parent loop is eating processor time. top command gives this:

enter image description here

Here the answer says:

As an additional bonus, the loop will block on waitpid while children are running, so you don't need a busy loop while you wait.

But for me it doesn't block. What's wrong?

Answer

Andomar picture Andomar · Jun 7, 2013

You're passing the WNOHANG flag, which makes the call non-blocking. Remove this flag and waitpid will wait at 0% CPU until the child quits.

If you take this approach, you could simplify the code. There's no need to loop until a child is finished, because the blocking waitpid call will do that for you:

for (my $i = 0; $i < $n; ++$i) {
    if ($children_pids[$i] > 0) {
          waitpid($children_pids[$i], 0);
          print "child done\n";
          $children_pids[$i] = 0;
    }
}

Alternatively, change the sleep call to wait for one second. Then your program will check for finished children every second, without pushing up CPU usage.