Skip to main content

Threads vs processes vs asyncio

advanced18 min readLesson 124 of 169

A repeatable decision procedure โ€” with backpressure as a first-class concern.

Choosing a model: a decision procedure

Run this procedure per workload โ€” never per fashion trend:

  1. Characterize the work. Mostly waiting (network/disk/subprocess) or mostly computing (parsing, math, image processing)?
  2. Waiting โ†’ try asyncio first (thousands of concurrent tasks on one thread โ€” see the next module); if the libraries you need are blocking, use a ThreadPoolExecutor.
  3. Computing, pure Python โ†’ ProcessPoolExecutor (or workers outside the process). Threads buy nothing under the GIL.
  4. Computing inside C extensions that release the GIL โ†’ threads work.
  5. Measure. Wall-clock the candidate against the baseline on real data sizes. Threads and processes both have setup costs that can dominate small workloads โ€” a pool of 8 workers on 3 tiny tasks is slower than serial code.

Backpressure thinking: an unbounded task queue grows until memory dies. Bound the queue (queue.Queue(maxsize=N)), cap pool max_workers, and let producers block or shed load. Concurrency without limits is an outage story.

And the free-threaded future (prose): with PEP 703 builds, CPU-bound threads parallelize without processes โ€” but shared-state races become real instead of GIL-masked, so the synchronization skills of this module become MORE important, not less.

Now practice

Queue PracticeProducer/consumer with queue.Queue โ€” task_done discipline and deadlock awareness.1 challenge ยท ยท ~22 minProject: Concurrent Job ProcessorRetry-aware concurrent job runner โ€” the seed of every real worker system.1 challenge ยท ยท ~30 min