Languages like C and C++ lack built-in support for concurrency.
Unix employs a distributed process model.
Basic operations:
int status ;
if ( fork () == 0) {
/ * child 1 * /
execl ( " ./ prog1 " , " prog1 " , 0);
} else if ( fork () == 0) {
/ * child 2 * /
execl ( " ./ prog2 " , " prog2 " , 0);
} else {
/ * parent * /
wait (& status );
wait (& status );
}
This program forks into two processes. The child process runs a program prog1, then finishes. The parent moves into the first else, then forks again into two processes. The second child runs a program prog2, then finishes. The parent moves to the final else, where it waits for the two children to finish.
close STDOUT ;
close STDERR ;
$pid = fork ();
if (! defined ( $pid )) {
die ( " Cannot fork : $ !\ n " );
}
if ( $pid ) {
# parent : issue web page to confirm that assignment has been received
⋮
} else {
# child : launch a possibly lengthy autograde program
$gradeResults = _autograde . pl - stop cleanup $grader $login_ ;
$logFile = $assignmentDirectory . " grading . log " ;
open ( GRADELOG , " >> $logFile " )
}} die ( " Could not append to $logFile : $ !\ n " );
print GRADELOG " $gradeResults \ n " ;
close GRADELOG ;
}
Here is part of the Perl script that gets launched when you submit programming assignments from the course’s web-based submissions pages:
This is run as a CGI script when you click the submit button on the assignment submission pages. Now, the actual auto-grading may take several minutes, but web browsers will time out long before then. So the script forks to create a new process. The new child runs the auto-grader. In the meantime, the writes out a quick "Thank you for submitting" page that the browser can show immediately.
Unix processes can communicate via a pipe, a communication path that appears to the program as a pair of files, one for input, the other for output.
Reading from an empty pipe will block a process until data is available (written into the pipe by another process).
char str [6];
int mypipe [2];
int status ;
pipe ( mypipe ); / * establishes a pipe . * /
/ * We read from mypipe [0] and * /
/ * write to mypipe [1] * /
if ( fork () == 0) { / * child * /
read ( mypipe [0], str , 6);
printf ( str );
} else { / * parent * /
write ( mypipe [1], " Hello " , 6);
wait (& status );
}
close mypipe [0];
close mypipe [1];
This is "C" style I/O. Files are identified by an integer file handle, so out array of two ints is actually a pair of file handles. Each process can read from mypipe[0] and write to mypipe[1], though in this particular example one process does all the writing and the other all the reading.
This approach to process communication is
Library-based concurrency tends to be non-portable.