Threads and Networking Essentials
Network input and output is blocking I/O, because those operations can block for indefinite periods of time, so network programs must be prepared for waiting.
A Server Waiting on Its Clients
Imagine a server that talks to several clients at once. Most of the time its threads are not calculating anything. They are waiting for network input and output to finish. Before you learn how the server should be designed, make a prediction. Suppose the server runs on a machine with a small number of processors, and each of its threads is blocked around 90 percent of the time while it waits for network traffic. How many threads would you expect the server to want, compared with the number of processors?
What do you think happens?
Each thread is blocked about 90 percent of the time. How many threads would you expect the server to want, compared with the number of processors?
Reveal answer
Roughly ten threads per processor, which means many more threads than processors.
The source's rule of thumb for blocking I/O is about one active thread per processor. If each thread is active only about 10 percent of the time, then about ten threads are needed for each processor to keep roughly one thread active on it at any moment. The exact number depends on the measured blocked fraction.
Why Network Reads Can Stall
Network input and output is called blocking I/O because those operations can block for indefinite periods of time. A program that asks the network for data may have to wait an unpredictable amount of time before the data arrives.
The source is clear that waiting is not an unusual event for network code. It is part of how such code normally behaves. A network program therefore has to be written with that waiting in mind, rather than assuming that every read returns immediately with data.
Blocking is not always a problem. A command line program that reads text typed by the user can simply stop and wait, and user input is another form of blocking input and output. What separates a network program from that case is what else the program could be doing during the wait. A program with nothing else to do can afford to wait. A program with other work available benefits from letting some parts keep running while other parts are blocked.
Tracing One Thread Through a Network Read
Follow a single thread through one network read. The thread asks the network for data. It then has to wait for an unpredictable amount of time. When the data arrives, the thread can continue. The diagram below shows these steps as messages between the thread and the network. The source does not describe the internal mechanism that wakes the thread when data arrives, so the diagram shows only the sequence of events, not how the wake-up happens.
While the thread waits, it does not compete for the processor. Only the active, unblocked threads compete for processing time. This is the key fact behind everything else in this article. A waiting thread takes up a place in the program's design, but it does not take up processor time.
Two Goals for Threads
Threads can be added to a program for two different goals, and the right number of threads depends on which goal you have. The first goal is computational speedup: using threads to make calculations finish sooner. The second goal is handling blocking input and output: letting the program keep working while some of its threads wait.
For computational speedup, one thread per available processor is the natural choice. If only one processor exists, adding more threads gives no speedup. It actually slows the program down because of the overhead of creating and managing the threads. Speedup is limited by how many threads can make real progress at the same time.
For blocking input and output, the threads are not all trying to compute at once. Many of them are waiting, and a waiting thread does not compete for the processor. This means a program can have many more threads than processors without overloading the processors, because most of those threads are not running at any given moment.
| Question | Threads for computational speedup | Threads for blocking I/O |
|---|---|---|
| Main goal | Make calculations finish sooner | Keep the program working while parts of it wait |
| Typical thread count | About one per available processor | Can be many more than processors |
| What limits the count | How many threads can make real progress at once | How much of the time threads spend waiting |
| Effect of extra threads on one processor | No speedup, added overhead | Useful, because blocked threads do not compete for the processor |
Two goals for threads and the thread counts they call for
Why the One-Thread-Per-Processor Rule Breaks Down
A common rule of thumb says that a program should never run more threads than the computer has processors. For many programs this is a sensible starting point, but it comes from one specific situation: threads that do computation and want to run at full speed. Applied to every program, it ignores what the threads are actually doing. The network case is the one where threads spend much of their time waiting.
This is why the useful number of threads for a blocking program is tied to how much of the time the threads spend waiting, not only to how many processors are present. The more a thread waits, the more threads the program can keep in its design without overloading the processors.
Estimating a Thread Count for a Waiting Server
The rough target for blocking I/O is about one active thread per processor. Because mostly blocked threads are inactive for much of the time, that target can mean many more threads in total. The calculation below turns the blocked fraction into a rough thread count. It is an estimate, not a fixed answer.
Estimating Threads for a Server with Four Processors
A server runs on a machine with 4 processors. Each of its threads is blocked about 90 percent of the time waiting for network input and output. Roughly how many threads can the server make use of?
- 1Find the active fraction
If a thread is blocked 90 percent of the time, it is active about 10 percent of the time, which is 0.10 of its time.
- 2Apply the one-active-thread-per-processor target
The rough target is about one active thread per processor. With 4 processors, the target is about 4 active threads at any moment.
- 3Convert active threads into total threads
If each thread is active only about 10 percent of the time, about 1 divided by 0.10, or 10, threads are needed for each active slot on average. Across 4 processors, that gives about 4 times 10, or 40, threads.
- 4Check the assumption
The result depends on the measured blocked fraction. If the threads were blocked less often, the same reasoning would produce a smaller number. The number 40 is not a universal answer for every server.
About 40 threads, roughly ten per processor, is a plausible starting point for this server. The actual number should follow from how much blocking is measured in the program.
Following a Thread's States
A thread in a network program moves between two situations. While it waits for data, it is blocked and does not compete for processor time. When the data it needs has arrived, it is active and competes for processing time again. The diagram shows this back-and-forth. The source does not give a detailed list of thread states, so this diagram keeps to the two situations the source describes.
Because a blocked thread does not compete for processor time, a program with many threads can keep most of them in the blocked situation at any given moment. The processors are then shared among the few threads that are active. This is the reason many threads can be useful for network code even when there are few processors.
Two Servers and a Client
The source contrasts two network programs. A client that sends requests to a server may be fine with a single thread, if it has nothing else to do while it waits for responses. A server is different. It can usually be connected to several clients at the same time, and while it waits for data from one client, it can communicate with the others.
When the server gives each client connection its own thread, a block on one connection does not stop the server from talking to the rest. This is the situation the source describes as especially valuable for servers that serve several clients at once.
Mistakes About Thread Counts
Common mistake Always using no more threads than processors
What it looks likeA server with mostly blocked threads is limited to the processor count, even though most threads are waiting at any moment.
Why it failsThe rule of thumb comes from computation-heavy threads that want to run at full speed. It ignores how much time threads spend blocked.
Do this insteadStart from the rough target of one active thread per processor, then account for the blocked fraction to estimate the total.
Common mistake Assuming more threads always means more speedup
What it looks likeAdding threads to a calculation on a machine with a single processor, expecting it to finish faster.
Why it failsExtra threads for computation add overhead without speedup when there is only one processor.
Do this insteadFor computational speedup, aim for about one thread per available processor.
Common mistake Treating a network read as if it always returns quickly
Why it failsNetwork input and output is blocking I/O because it can block for indefinite periods of time.
Do this insteadWrite the program so that it is prepared for waiting, and so that other work can continue during the wait.
The estimate from the blocked fraction is only as good as the measurement behind it. The source notes that if threads were rarely blocked, the same calculation would give a much smaller number. A thread count should follow from measured blocking, not from a fixed guess.
Checking Your Thread Count Reasoning
A client program on a single-processor machine spends almost all of its time waiting for replies from a server. A separate compute job on the same machine spends almost all of its time calculating. Which program would benefit more from having several threads, and why? Explain your reasoning in terms of blocked threads and processor time.
Hints
- Ask whether the threads in each program are mostly waiting or mostly computing.
- Think about whether a blocked thread competes for processor time.
- For the compute job, consider what extra threads do on a single processor.
Key Takeaways
- Network input and output is called blocking I/O because those operations can block for indefinite periods of time, so network programs must be prepared for waiting.
- Threads for computational speedup should roughly match the number of available processors, because extra threads add overhead without speedup on a single processor.
- Threads for blocking I/O can outnumber processors many times over, because blocked threads do not compete for processor time.
- A rough estimate for a blocking program is about one active thread per processor, scaled up by the inverse of the active fraction, and the estimate must be based on measured blocking.