Python Programming from First Principles: Learn to Code, Think Computationally, and Solve Real-World Problems
PDFEPUB
BOOK 000 · CHAPTER 8 · Threads: Best for I/O-Bound Tasks

Threads: Best for I/O-Bound Tasks

Threads are small workers that run inside the same Python process and share memory. They’re lightweight, easy to create, and ideal when work is mostly waiting.

Imagine downloading ten web pages. Suppose each download spends most of its time waiting for the server to respond. Threads can overlap this waiting time, finishing the job much earlier.

Real-World Analogy

A single cook in a kitchen needs to wait while water boils. But if you hire several assistants, they can: - set the table - fetch ingredients - prepare sauces

…all while the water still boils.

This is exactly how threads hide waiting time.

Example: Parallel “Network” Requests Using Threads

This example simulates network calls using time.sleep().

If each task waits, threads help overlap that waiting.

pool.submit schedules a function and returns a future, an object representing its eventual result. as_completed supplies completed futures; result() retrieves a value or raises the task’s exception.

Code Example

import time
from concurrent.futures import ThreadPoolExecutor, as_completed

def fake_fetch(url, delay=0.2):
    """Simulate a slow network request by sleeping briefly."""
    time.sleep(delay)
    return f"OK {url} ({delay:.1f}s)"

urls = [f"https://example.org/page/{i}" for i in range(5)]

start = time.perf_counter()

# Create a pool of threads
with ThreadPoolExecutor(max_workers=5) as pool:
    # Schedule each fetch in its own thread
    futures = [
        pool.submit(fake_fetch, u, 0.2 + (i % 3) * 0.1)
        for i, u in enumerate(urls)
    ]
    # Print results as they finish
    for fut in as_completed(futures):
        print(fut.result())

elapsed = time.perf_counter() - start
print(f"Total time ~ {elapsed:.2f}s (overlapping waits)")
Listing 427 — Threads: Best for I/O-Bound Tasks

What’s Happening?

  • Each call to fake_fetch() sleeps for a moment (like waiting on a slow network).
  • The thread “releases” the CPU during sleep.
  • Another thread immediately starts its work.
  • All threads overlap their waiting time, so the whole job finishes quickly.

Change max_workers=5 to max_workers=1 in the executor call and run the complete program again.

Now the script must complete each request sequentially—much slower.

This is the heart of concurrency: hiding wait time.

Completion order and measured time can vary; these are simulated waits, not HTTP requests. The next section changes the workload from waiting to computing.