What am I missing in python-multiprocessing/multithreading?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
The usual thing people are missing when comparing Python multithreading and multiprocessing is the Global Interpreter Lock, or GIL. Threads in CPython are excellent for overlapping I/O, but they do not generally let CPU-bound Python code run in parallel on multiple cores.
Processes do. That is why the right answer is often: use threads for I/O-bound work, use processes for CPU-bound work, and do not expect them to solve the same problem equally well.
Why Threads Often Disappoint for CPU Work
In CPython, only one thread can execute Python bytecode at a time because of the GIL. So code like this may use multiple threads, but not actually speed up a CPU-heavy pure-Python loop.
This creates concurrency, but not true multi-core execution for ordinary Python bytecode.
Where Threads Shine
Threads are still very useful when the program spends much of its time waiting on external systems such as:
- network requests
- disk I/O
- database calls
- sleeping or timers
While one thread waits, another can make progress.
For this kind of workload, threads can reduce overall wall-clock time nicely.
Why Processes Help CPU-Bound Work
multiprocessing launches separate processes, each with its own Python interpreter and memory space. That avoids the GIL bottleneck between those processes and allows true parallel CPU execution.
This is the standard answer for CPU-heavy pure-Python workloads that need multiple cores.
Processes Have Their Own Cost
Processes are not free. They have more overhead than threads because they do not share memory the same way. Sending large objects between processes can be expensive.
So the rule is not "multiprocessing is always better." It is "multiprocessing is usually better when the CPU work is heavy enough to justify the extra overhead."
Shared State Is Different
Threads share memory by default, which makes communication easy but synchronization risky. Processes isolate memory by default, which is safer in some ways but makes data sharing more explicit.
That means you should also choose based on how the workers need to communicate, not only on raw speed.
A Practical Rule of Thumb
Use this decision pattern:
- waiting on network or disk: threads or async I/O
- pure CPU-heavy Python work: processes
- native code libraries that release the GIL: threads may still help
That last point matters because libraries such as NumPy may release the GIL during heavy native work, which changes the performance story.
Common Pitfalls
- Expecting Python threads to speed up CPU-bound pure-Python loops in CPython.
- Reaching for multiprocessing without considering serialization and process-start overhead.
- Assuming threads and processes are interchangeable concurrency tools.
- Forgetting the
if __name__ == "__main__":guard when using multiprocessing on platforms that require it. - Benchmarking a tiny task and then drawing conclusions that do not hold for real workloads.
Summary
- The GIL is the main reason Python threads usually do not speed up CPU-bound pure-Python code.
- Threads are still very good for I/O-bound concurrency.
- Multiprocessing enables real parallel CPU execution across cores.
- Processes cost more overhead, so they are best when the CPU work is substantial.
- Choose threads, processes, or async tools based on the workload, not by habit.

