Our first example demonstrates how to spawn off PVM tasks and synchronize with them. The program spawns several tasks, three by default. The children then synchronize by sending a message to their parent task. The parent receives a message from each of the spawned tasks and prints out information about the message from the child tasks.
The fork-join program contains the code for both the parent and the child
tasks. Let's examine it in more detail. The very first thing the
program does is call pvm_mytid()
. This function must be called
before any other PVM call can be made. The result of the
pvm_mytid()
call should always be a positive integer. If it is
not, then something is seriously wrong. In fork-join we check the value
of mytid; if it indicates an error, we call pvm_perror()
and
exit the program. The pvm_perror()
call will print a message
indicating what went wrong with the last PVM call. In our example the
last call was pvm_mytid()
, so pvm_perror()
might print a
message indicating that PVM hasn't been started on this machine. The
argument to pvm_perror()
is a string that will be prepended to
any error message printed by pvm_perror()
. In this case we pass
argv[0], which is the name of the program as it was typed on the
command line. The pvm_perror()
function is modeled after the
Unix perror()
function.
Assuming we obtained a valid result for mytid, we now call
pvm_parent()
. The pvm_parent()
function will return the
TID of the task that spawned the calling task. Since we run the
initial fork-join program from the Unix shell, this initial task will
not have a parent; it will not have been spawned by some other PVM task
but will have been started manually by the user. For the initial
forkjoin task the result of pvm_parent()
will not be any
particular task id but an error code, PvmNoParent. Thus we can
distinguish the parent forkjoin task from the children by checking whether
the result of the pvm_parent()
call is equal to PvmNoParent. If
this task is the parent, then it must spawn the children. If it is not
the parent, then it must send a message to the parent.
Let's examine the code executed by the parent task. The number of
tasks is taken from the command line as argv[1]. If the number of
tasks is not legal, then we exit the program, calling pvm_exit()
and then returning. The call to pvm_exit()
is important because
it tells PVM this program will no longer be using any of the PVM
facilities. (In this case the task exits and PVM will deduce that the
dead task no longer needs its services. Regardless, it is good style
to exit cleanly.) Assuming the number of tasks is valid, forkjoin will
then attempt to spawn the children.
The pvm_spawn()
call tells PVM to start ntask tasks named
argv[0]. The second parameter is the argument list given to the
spawned tasks. In this case we don't care to give the children any
particular command line arguments, so this value is null. The third
parameter to spawn, PvmTaskDefault, is a flag telling PVM to spawn the
tasks in the default location. Had we been interested in placing the
children on a specific machine or a machine of a particular
architecture, then we would have used PvmTaskHost or PvmTaskArch for
this flag and specified the host or architecture as the fourth
parameter. Since we don't care where the tasks execute, we use
PvmTaskDefault for the flag and null for the fourth parameter.
Finally, ntask tells spawn how many tasks to start; the integer
array child will hold the task ids of the newly spawned children. The
return value of pvm_spawn()
indicates how many tasks were
successfully spawned. If info is not equal to ntask, then some error
occurred during the spawn. In case of an error, the error code is
placed in the task id array, child, instead of the actual task id.
The fork-join program loops over this array and prints the task ids or any error
codes. If no tasks were successfully spawned, then the program exits.
For each child task, the parent receives a message and prints out
information about that message. The pvm_recv()
call receives a
message (with that JOINTAG) from any task.
The return value of pvm_recv()
is an integer indicating a
message buffer. This integer can be used to find out information about
message buffers. The subsequent call to pvm_bufinfo()
does just
this; it gets the length, tag, and task id of the sending process for
the message indicated by buf. In fork-join the messages sent by the
children contain a single integer value, the task id of the child
task. The pvm_upkint()
call unpacks the integer from the
message into the mydata variable. As a sanity check, forkjoin tests
the value of mydata and the task id returned by pvm_bufinfo()
.
If the values differ, the program has a bug, and an error message
is printed. Finally, the information about the message is printed, and
the parent program exits.
The last segment of code in forkjoin will be executed by the child
tasks. Before placing data in a message buffer, the buffer must be
initialized by calling pvm_initsend()
. The parameter
PvmDataDefault indicates that PVM should do whatever data conversion is
needed to ensure that the data arrives in the correct format on the
destination processor. In some cases this may result in unnecessary
data conversions. If the user is sure no data conversion will be needed
since the destination machine uses the same data format, then he can
use PvmDataRaw as a parameter to pvm_initsend()
. The
pvm_pkint()
call places a single integer, mytid, into the
message buffer. It is important to make sure the corresponding unpack
call exactly matches the pack call. Packing an integer and unpacking
it as a float will not work correctly. Similarly, if the user packs two
integers with a single call, he cannot unpack those integers by
calling pvm_upkint()
twice, once for each integer. There must
be a one to one correspondence between pack and unpack calls. Finally,
the message is sent to the parent task using a message tag of JOINTAG.