Threads vs Processes

asyncio, threads, processes — three ways to do "at the same time", each right for a different enemy: waiting, latency, and the GIL.

▶ Watch this reel

What you'll learn

  1. The GIL
  2. Threads for I/O
  3. Processes for CPU
  4. Choosing & executors

Remember this

The GIL

Threads — I/O-bound

Processes — CPU-bound

Decision

Code: The hybrid reality: async app + thread/process adapters

import asyncio
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

# blocking sync SDK you CANNOT change
def legacy_translate(text: str) -> str:
    return old_sdk.translate(text)   # blocks, no async version

def cpu_heavy_score(text: str) -> float:
    return sum(ord(c) for c in text) % 100 / 100  # pretend: pure-Python math

async def pipeline(texts: list[str]) -> list[str]:
    loop = asyncio.get_running_loop()

    with ThreadPoolExecutor(8) as tpool, ProcessPoolExecutor(4) as ppool:
        # 1 · blocking SDK off the event loop (threads)
        translations = await loop.run_in_executor(
            tpool, lambda: list(map(legacy_translate, texts)))

        # 2 · CPU math on real cores (processes)
        scores = await loop.run_in_executor(
            ppool, lambda: list(ppool.map(cpu_heavy_score, texts)))

    # 3 · back on the loop: async I/O continues
    return await asyncio.gather(
        *[notify_api(t, s) for t, s in zip(translations, scores)])