Using multiprocessing.Process with a maximum number of simultaneous processes
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Launching a new multiprocessing.Process for every job can overwhelm a machine long before the work is finished. CPU time, memory usage, open file descriptors, and process startup overhead all become bottlenecks if concurrency is unbounded. The practical solution is to cap the number of simultaneous processes and choose a coordination pattern that matches the workload.
The Simplest Limit Is a Process Pool
If your goal is just “run these jobs with at most N workers,” a process pool is usually better than hand-managing Process objects.
Here, max_workers=4 is the limit. The executor reuses worker processes instead of spawning a fresh one for each task, which reduces overhead.
Manual Control With multiprocessing.Process
Sometimes you need lower-level control. In that case, you can keep a list of running processes and wait when the cap is reached.
This approach works, but it is more fragile than using a pool. Choose it only when you genuinely need direct process lifecycle control.
Use a Semaphore When Only Part of the Work Must Be Limited
A semaphore is useful when a process can do some preparation freely, but must enter a limited critical section to protect a scarce resource.
In that example, more than two processes may exist at once, but only two can execute the protected section concurrently.
Collect Results Without Shared State Confusion
When manually creating processes, result collection often becomes the next challenge. A Queue is usually the safest way to bring results back to the parent process.
This avoids accidental races on shared lists or dictionaries.
Choosing the Right Concurrency Limit
For CPU-bound work, start near the number of available CPU cores and measure. More processes do not automatically mean more throughput. Once the CPU is saturated, extra workers mostly add context-switching overhead.
For I/O-heavy workloads, a higher process count can make sense, but multiprocessing may not be the best tool at all. Threads or async I/O may be simpler if the main wait is network or disk latency rather than CPU time.
The right limit is not a rule of thumb you memorize once. It is something you benchmark on the hardware and data that matter to your application.
Common Pitfalls
The first pitfall is skipping the if __name__ == "__main__": guard. On platforms that use spawn-based process startup, forgetting that guard can recursively re-import the module and create broken behavior.
Another mistake is starting unbounded processes in a loop. The code looks fine with ten jobs and then collapses with ten thousand.
Developers also underestimate serialization cost. Every argument and result passed between processes must be pickled, so large payloads can erase the benefit of parallel execution.
Finally, do not use multiprocessing for work that is mostly waiting on external I/O unless you have measured that it helps. It is heavier than threads and often unnecessary for that case.
Summary
- Limit process concurrency to protect CPU, memory, and system stability.
- Prefer
ProcessPoolExecutoror a pool when you only need capped parallel job execution. - Use manual
Processmanagement only when you need explicit lifecycle control. - Semaphores are useful when only a limited section of work must be restricted.
- Benchmark the worker count instead of assuming more processes always means faster code.

