7
8 while (1) {
9 printf ('Alarm> ');
10 if (fgets (line, sizeof (line), stdin) == NULL) exit (0);
11 if (strlen (line) <= 1) continue;
12 alarm = (alarm_t*)malloc (sizeof (alarm_t));
13 if (alarm == NULL)
14 errno_abort ('Allocate alarm');
15
16 /*
17 * Parse input line into seconds (%d) and a message
18 * (%64[^
]), consisting of up to 64 characters
19 * separated from the seconds by whitespace.
20 */
21 if (sscanf (line, '%d %64[^
J',
22 &alarm->seconds, alarm->message) < 2) {
23 fprintf (stderr, 'Bad command
');
24 free (alarm);
25 } else {
2 6 status = pthread_create (
27 &thread, NULL, alarm_thread, alarm);
28 if (status != 0)
29 err_abort (status, 'Create alarm thread');
30 }
31 }
32 }
1.5.4 Summary
A good way to start thinking about threads is to compare the two asynchronous versions of the alarm program. First, in the fork version, each alarm has an independent address space, copied from the main program. That means we can put the seconds and message values into local variables—once the child has been created (when fork returns), the parent can change the values without affecting the alarm. In the threaded version, on the other hand, all threads share the same address space—so we call malloc to create a new structure for each alarm, which is passed to the new thread. The extra bookkeeping required introduces a little complexity into the threaded version.
In the version using fork, the main program needs to tell the kernel to free resources used by each child process it creates, by calling waitpid
or some other member of the wait 'family.' The alarm_fork.c program, for example, calls waitpid
in a loop after each command, to collect all child processes that have completed. You do not need to wait for a thread unless you need the thread's return value — in alarm_thread.c, for example, each alarm thread
In the threaded version, the 'primary activities' (sleeping and printing the message) must be coded in a separate routine. In alarm.c and alarm_fork.c, those activities were performed without a call. In simple cases such as our alarm program, it is often easier to understand the program with all code in one place, so that might seem like an advantage for alarm_fork .c. In more complicated programs, though, it is rare that a program's 'primary activities' are so simple that they can be performed in a single routine without resulting in total confusion.
In a real alarm program, you wouldn't want to create a process for each alarm. You might easily have hundreds of alarms active, and the system probably wouldn't let you create that many processes. On the other hand, you probably can create hundreds of threads within a process. While there is no real need to maintain a stack and thread context for each alarm request, it is a perfectly viable design.
1.6 Benefits of threading
''O Looking-Glass creatures,'quoth Alice, 'draw near!
Tis an honour to see me, a favour to hear:
'Tis a privilege high to have dinner and tea
Along with the Red Queen, the White Queen, and me!''
Some advantages of the multithreaded programming model follow:
1. Exploitation of program parallelism on multiprocessor hardware. Parallelism is the only benefit that requires special hardware. The others can help most programs without special hardware.
2. More efficient exploitation of a program's natural concurrency, by allowing the program to perform computations while waiting for slow I/O operations to complete.
3. A modular programming model that clearly expresses relationships between independent 'events' within the program.
These advantages are detailed in the following sections.
1.6.1 Parallelism
On a multiprocessor system, threading allows a process to perform more than one independent computation at the same time. A computation-intensive threaded application running on two processors may achieve nearly twice the performance of a traditional single-threaded version. 'Nearly twice' takes into account the fact that you'll always have some overhead due to creating the extra thread(s) and performing synchronization. This effect is often referred to as 'scaling.' A two-processor system may perform 1.95 times as fast as a single processor, a three- processor system 2.9 times as fast, a four-processor system 3.8 times as fast, and so forth. Scaling almost always falls off as the number of processors increases because there's more chance of lock and memory collisions, which cost time.
FIGURE 1.1
Scaling can be predicted by 'Amdahl's law,' which is shown in Figure 1.1. In the equation,