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:
- Characterize the work. Mostly waiting (network/disk/subprocess) or mostly computing (parsing, math, image processing)?
- Waiting โ try
asynciofirst (thousands of concurrent tasks on one thread โ see the next module); if the libraries you need are blocking, use aThreadPoolExecutor. - Computing, pure Python โ
ProcessPoolExecutor(or workers outside the process). Threads buy nothing under the GIL. - Computing inside C extensions that release the GIL โ threads work.
- 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.