Fork yeah! Part 2
In part one of this article I described how to use Perl’s fork
function to write concurrent programs. Here are a couple of other ways.
WNOHANG
Usually waitpid
is a blocking call which returns when a child process exits:
#!/usr/bin/perl
my $pid = fork;
if ($pid == 0) {
sleep 1;
exit;
}
waitpid $pid, 0;
In this example the second argument to waitpid
is 0, which is the flags argument. But what if we wanted to do additional processing in the parent process, whilst still occasionally checking for reaped children?
The POSIX module includes the WNOHANG
constant which makes the waitpid
call non-blocking. Instead it returns immediately with an integer:
- -1 indicates no child process exists for that process id, or none at all if pid of -1 was supplied
- 0 indicates there is a child process but it has not changed state yet
2-32768 is the pid of the child process which exited (it will never be 1 - that’s init)
#!/usr/bin/perl use POSIX 'WNOHANG'; my $pid = fork; if ($pid == 0) { sleep 1; exit; } my $kid; do { # do additional processing sleep 1; $kid = waitpid -1, WNOHANG; } while ($kid == 0);
I’ve changed the code to wait for the child to exit in a do
while
loop, each iteration calling waitpid
with WNOHANG
to allow me to undertake any additional processing I want to in the body of the do
block. Without WNOHANG
, this would loop once per reaped child; with it, I can still collect exiting child processes, but the loop may iterate thousands of times in the meantime.
WUNTRACED
The POSIX module provides waitpid constants and macros. The other constant is WUNTRACED
which causes waitpid
to return if the child process is stopped (but not exited).
The parent can then take appropriate action: it might record the stopped process somewhere, or choose to resume the child by sending it a continue signal (SIGCONT):
#!/usr/bin/perl
use POSIX ':sys_wait_h';
$SIG{INT} = sub { exit };
my $pid = fork;
if ($pid == 0) {
while (1) {
print "child going to sleep\n";
sleep 10;
}
}
print "child PID: $pid\n";
while (1) {
my $kid = waitpid $pid, WUNTRACED;
if (WIFSTOPPED(${^CHILD_ERROR_NATIVE})) {
print "sending SIGCONT to child\n";
kill 'CONT', $kid;
}
else {
exit;
}
}
I’ve used the group sys_wait_h
to import multiple symbols from the POSIX module. This time, both child and parent are in infinite while loops. If I pause the child by sending it SIGSTOP, waitpid
will return. The parent tests whether the child was stopped with the macro WIFSTOPPED
, if so it sends SIGCONT to the child via kill
, resuming it.
Running the script as wuntraced.pl
:
$ ./wuntraced.pl
child PID: 15013
child going to sleep
In another terminal I send SIGSTOP to the child:
kill -s STOP 15013
And the parent resumes the child:
sending SIGCONT to child
child going to sleep
Both processes keep running until I send SIGINT to the child:
$ kill -s INT 15013
Combining Constants
WNOHANG and WUNTRACED are not mutually exclusive: I can change waitpid’s behavior by combining both constants into a single flag value with binary or (|
).
#!/usr/bin/perl
use Data::Dumper 'Dumper';
use POSIX ':sys_wait_h';
$SIG{INT} = sub { exit };
my %pids;
for (1..3) {
my $pid = fork;
if ($pid == 0) {
sleep 1 while 1;
}
else {
$pids{$pid} = {
duration => undef,
started => time,
stops => 0,
};
}
}
while (1) {
my $kid = waitpid -1, WNOHANG | WUNTRACED;
# do additional processing
print Dumper(\%pids);
sleep 3;
if (WIFSTOPPED(${^CHILD_ERROR_NATIVE})) {
$pids{$kid}->{stops}++;
kill 'CONT', $kid;
}
elsif ($kid > 0) {
my $exit_time = time;
$pids{$kid}->{duration} = $exit_time - $pids{$kid}->{started};
}
elsif ($kid == -1) {
exit;
}
}
This code forks 3 children which run forever, and the parent tracks statistics for each child: the start time, duration and number of times it received SIGSTOP. The parent will resume any stopped child with SIGCONT. The parent prints the stats every 3 seconds, and exits when all the children have exited.
Running this code, I can play around by sending SIGSTOP and SIGINT to different child processes and watch the stats update. Although this is a simple example, by using WNOHANG
and WUNTRACED
you can see how they change the parent process’s role from a passive observer to a supervisor which can actively manage its sub-processes.
Tags
David Farrell
David is a professional programmer who regularly tweets and blogs about code and the art of programming.
Browse their articles
Feedback
Something wrong with this article? Help us out by opening an issue or pull request on GitHub