Concepts / Threads and Networking Essentials

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.

  • Programming

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?

  • About the same as the number of processors

  • Roughly ten threads per processor

  • Exactly 90 threads in total

  • Only one thread

Reveal answer
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

Definition

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.

Threadthe program's workerNetworkremote sendercalls network readdata arrives after unknown wait
What happens to a thread between calling a network read and the moment data arrives?

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.

Finish calculationssoonerKeep working whilethreads waitAbout one thread perprocessorMany more threadsthan processorsExtra threads addoverhead onlyBlocked threads donot use the processor
In both cases the question is how many threads can actually be doing work at the same time, and the answer differs because blocked threads do not count as doing work.
QuestionThreads for computational speedupThreads for blocking I/O
Main goalMake calculations finish soonerKeep the program working while parts of it wait
Typical thread countAbout one per available processorCan be many more than processors
What limits the countHow many threads can make real progress at onceHow much of the time threads spend waiting
Effect of extra threads on one processorNo speedup, added overheadUseful, 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.

Worked example

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?

  1. 1
    Find 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.

  2. 2
    Apply 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.

  3. 3
    Convert 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.

  4. 4
    Check 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.

Result

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.

Activecompetes for processor timeBlockedwaiting for network datacalls network readdata arrives
When a thread calls a network read, it moves from active to blocked. When data arrives, it moves back to active and competes for processor time again.

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 like

    A server with mostly blocked threads is limited to the processor count, even though most threads are waiting at any moment.

    Why it fails

    The 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 instead

    Start 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 like

    Adding threads to a calculation on a machine with a single processor, expecting it to finish faster.

    Why it fails

    Extra threads for computation add overhead without speedup when there is only one processor.

    Do this instead

    For computational speedup, aim for about one thread per available processor.

  • Common mistake

    Treating a network read as if it always returns quickly

    Why it fails

    Network input and output is blocking I/O because it can block for indefinite periods of time.

    Do this instead

    Write the program so that it is prepared for waiting, and so that other work can continue during the wait.

Edge case

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

MEDIUM

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.