Multiprocessor Considerations


Computationally intensive tasks are characterized by intensive processor usage with relatively few I/O operations. The ongoing challenge with these applications is to improve the performance. You can do this by getting a faster computer, choosing a more efficient algorithm, improving the implementation, or by using more processors. Improving the performance is accomplished by tuning techniques, which are covered in Development Environment Overview in Chapter 7, Creating the Development Environment. Using more processors can mean taking advantage of a symmetric multiprocessor (SMP) computer or using distributed computing with multiple networked computers. This section looks at these techniques and how to port UNIX implementations to Win32.

Process and Thread Solutions

How to take advantage of symmetric multiprocessors is discussed in this section. On a multiprocessor UNIX system, the typical approach is to spawn several processes to run on each of the available processors. Each processor can run one process simultaneously with all the other processors to achieve true parallelism. The technique usually employed is to have an administrative parent process that starts a computational child process for each available processor.

The other technique for exploiting SMP hardware is to use POSIX threads on UNIX releases that support them. The details of converting POSIX threads to Windows were covered in Threads earlier in this chapter. If your computationally intensive application already uses POSIX threads for parallelism, you can use the porting techniques covered there.

The classic parent process code on UNIX will call fork() to create a child process for each processor and wait for the children to finish computing. Multiple processes and maintaining a parent-child relationship on Windows is demonstrated here. The code discussed in Waiting for a Spawned Process earlier in this chapter is extended here to create multiple children:

 #include <unistd.h> #include <stdio.h> #include <sys/types.h> #include <sys/wait.h> #define MAX_PROCS 4 int main() {     pid_t pid;     pid_t children[MAX_PROCS];     int   child_running[MAX_PROCS];     int   tstat, ii, running;     for (ii = 0; ii < MAX_PROCS; ++ii)     {         pid = fork();         switch(pid)          {         case -1:             perror("fork failed");             child_running[ii] = 0;             break;         case 0:             printf("Im child #%d\n", ii);             exit(0);         default:             children[ii] = pid;             child_running[ii] = 1;             break;         }     } // wait for children to finish     running = MAX_PROCS;     while (running) {         pid = wait(&tstat);         for (ii = 0; ii < MAX_PROCS; ++ii) { // determine which child             if (pid == children[ii]) { // child done                 child_running [ii] = 0;                 running;                 printf("child #%d finished\n", ii);             }         }     }     exit(0); } 

The first problem in porting this type of code to Win32 is to do something withthe fork() call. Recall from the sections on code migration in Processes (earlierin this chapter) that Windows starts processes with the CreateProcess call, which starts a new process and the application running in it. This combines both the fork() and the exec () calls.

The child process must have some way to communicate with the parent to get the data it needs to work with, to update status, or to return results. There are several ways this can be done. If the child process continues with the same application, the data will probably already be set up in global variables . If the child executesa different application, the data may be transferred by using pipes, message queues, shared memory, or even files. To keep the following example simple, the parent application passes all the required data by using the environment set up for each child. This data is used to connect to common events that allow the parent to signal the children to perform an action, or exit.

Winforks Example

The Winforks.exe example follows the UNIX program outlined earlier. The parent creates a number of child processes that do the parts of some jobs in parallel. The parent waits for the child processes to exit.

The parent passes the data necessary for the child to execute in the environment set up for the child. The environments are created from scratch by the parent, and none of the existing environment is copied . This technique can be used to pass file names , flags, and other small amounts of data to the child processes. However, it can only be used to pass data to the child and cannot be used to retrieve data from the child. The details of passing the results back are not shown here, but if the initial data input is from a file, then the output file could also be specified to the child and retained by the parent.

This program also handles error indications from the child processes and stops those that are still executing on error. In Windows, killing a process is immediate, and the process is not informed of the action, so it cannot perform any cleanup operations. To allow the child process a chance to clean up and close any resources it is using, the parent passes the name of an exit event object to each child. If the child detects this exit event, it can clean up and stop gracefully.

This sample has three files. The first is a shared include file, Both.h, that contains the names of the environment variables that the parent sets and the child reads. The child process code, ChildExe.c, contains only a main() function, which gets the initialization data from the environment and loops until it is done.

The parent process file, WinForks.c, contains these definitions and functions:

  • A child process information structure to hold the child process handle, exit event, and other information.

  • A main() function that creates the child processes and waits for them to exit.

  • A ChildStart function to start a child process. This takes the place of the UNIX fork() call. This function creates the environment passed to the child and the exit event. Finally, it sets up the information necessary to start the child and calls CreateProcess .

  • A ChildCleanup function to release resources when a child exits.

  • A function to stop a child when an error occurs.

Winforks example code is shown in Appendix 9.11: Winforks Example.

Common Variations

If the application is using fork() to create multiple copies of itself (that is, if the child process does not use exec() to run another program), there are two completely different methods for approaching the migration. The first is to continue to use multiple processes and use shared memory to allow the child processes to access the parent s data. This approach works if the application is using the technique where global memory has already been set up for the child processes to access. Shared memory in Windows is straightforward and is covered in Shared Memory earlier this chapter.

The second and preferred solution where an application is using fork() to create copies of itself is to convert the program to use Win32 threads. Creating a threadin Windows is much faster and uses fewer system resources than creating a process. The conversion of this type of code to use threads should be uncomplicated.Of course, code to make the application thread-safe will likely be needed. Threading locks can be implemented using mutexes or critical sections. The details of howto do this are covered earlier in this chapter.

Another variation you might see in applications that depend on fork() is the useof pipes to transfer data and synchronize the parent and child processes. Again, converting this code to use Windows named pipes is easy. For details on converting UNIX pipes to Windows pipes, see Interprocess Communication earlier in this chapter.




UNIX Application Migration Guide
Unix Application Migration Guide (Patterns & Practices)
ISBN: 0735618380
EAN: 2147483647
Year: 2003
Pages: 134

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net