Async vs threading vs multiprocessing
Posted 2026-02-18 on the pythondeck.com blog
Three concurrency models, three different problems. The decision rule is short: shape of the workload, not personal preference.
Threading
One process, multiple OS threads, sharing memory. The GIL means only one thread runs Python bytecode at a time, so threading does NOT speed up pure-Python CPU work. It DOES speed up I/O-bound work, because threads release the GIL while waiting on sockets, files or subprocesses. Use threading for blocking I/O when an asyncio rewrite would be too disruptive.
Asyncio
One process, one thread, cooperative scheduling. await hands control back to the event loop while waiting on I/O, letting thousands of operations run concurrently. The catch is that the entire stack needs to be async-aware - a single sync database call blocks the whole loop. Use asyncio when you control the whole I/O stack and need very high concurrency.
Multiprocessing
Multiple OS processes, no shared memory, no GIL contention. This is the only option that gives true parallelism for pure-Python CPU-bound work. The cost is inter-process communication overhead - pass small messages, not gigabytes. Use multiprocessing for number crunching, image processing, parsing large corpora.
Decision tree
- CPU-bound, pure Python: multiprocessing (or rewrite the hot path with NumPy / Cython / Rust).
- I/O-bound, you control everything: asyncio.
- I/O-bound, mixing with blocking libraries: threading or asyncio +
to_thread. - Mixed: combine - a process pool of asyncio workers is a common pattern.