5.1. "Telling the Monkeys What to Do"
Most computers spend a lot of time doing nothing. If you start a system monitor tool and watch the CPU utilization, you'll see what I meanit's rare to see one hit 100 percent, even when you are running multiple programs.[*] There are just too many delays built into software: disk accesses, network traffic, database queries, waiting for users to click a button, and so on. In fact, the majority of a modern CPU's capacity is often spent in an idle state; faster chips help speed up performance demand peaks, but much of their power can go largely unused.
Early on in computing, programmers realized that they could tap into such unused processing power by running more than one program at the same time. By dividing the CPU's attention among a set of tasks, its capacity need not go to waste while any given task is waiting for an external event to occur. The technique is usually called parallel processing because many tasks seem to be performed at once, overlapping and parallel in time. It's at the heart of modern operating systems, and it gave rise to the notion of multiple active-window computer interfaces we've all come to take for granted. Even within a single program, dividing processing into tasks that run in parallel can make the overall system faster, at least as measured by the clock on your wall.
Just as important is that modern software systems are expected to be responsive to users regardless of the amount of work they must perform behind the scenes. It's usually unacceptable for a program to stall while busy carrying out a request. Consider an email-browser user interface, for example; when asked to fetch email from a server, the program must download text from a server over a network. If you have enough email and a slow enough Internet link, that step alone can take minutes to finish. But while the download task proceeds, the program as a whole shouldn't stallit still must respond to screen redraws, mouse clicks, and so on.
Parallel processing comes to the rescue here too. By performing such long-running tasks in parallel with the rest of the program, the system at large can remain responsive no matter how busy some of its parts may be. Moreover, the parallel processing model is a natural fit for such programs, and others; some tasks are more easily conceptualized and coded as components running as independent, parallel entities.
There are two fundamental ways to get tasks running at the same time in Pythonprocess forks and spawned threads. Functionally, both rely on underlying operating system services to run bits of Python code in parallel. Procedurally, they are very different in terms of interface, portability, and communication. At this writing, process forks are not supported on Windows under standard Python (more on this in the next section), but Python's thread support works on all major platforms. Moreover, the os.spawn family of calls provides additional ways to launch programs in a platform-neutral way that is similar to forks, and the os.popen and os.system calls can be used to portably spawn programs with shell commands.
In this chapter, which is a continuation of our look at system interfaces available to Python programmers, we explore Python's built-in tools for starting tasks in parallel as well as communicating with those tasks. In some sense, we've already started doing sothe os.system and os.popen calls introduced and applied in the prior two chapters are a fairly portable way to spawn and speak with command-line programs too. Here, our emphasis is on introducing more direct techniquesforks, threads, pipes, signals, and other launcher tools. In the next chapter (and in the remainder of this book), we use these techniques in more realistic programs, so be sure you understand the basics here before flipping ahead.