Multiprocessing runs work in separate interpreter processes. It is useful for independent CPU-bound Python calculations that can be serialized and are large enough to justify process startup and data-transfer costs.
After this lesson, you can protect process creation with the main guard, map a top-level function through ProcessPoolExecutor, collect exceptions from futures, and explain why start-method assumptions and shared mutable state make code fragile across platforms.
Measure the workload. Process parallelism can be slower when arguments and results are large or each task is too small.
| Workload | Starting Point |
|---|---|
| Many blocking I/O calls | ThreadPoolExecutor or asyncio |
| Independent CPU-heavy Python functions | ProcessPoolExecutor |
| Tiny fast operations | Ordinary sequential loop |
Child processes must be able to import the module without creating another pool recursively. Put process-starting code behind if __name__ == "__main__" and keep submitted functions importable at module level.
from concurrent.futures import ProcessPoolExecutor
def square(value: int) -> int:
return value * value
if __name__ == "__main__":
with ProcessPoolExecutor(max_workers=2) as pool:
print(list(pool.map(square, [2, 3, 4])))
[4, 9, 16]
The context manager shuts down worker processes after all mapped results are collected.
Arguments, return values, and submitted callables cross a process boundary through serialization. Open connections, locks from another context, local functions, and many extension objects cannot be transferred safely. Pass compact data and create process-local resources inside the worker when needed.
A submitted task failure is raised when result() is read. Keep a stable input identifier with each future so logs can name the failed unit without printing private payloads.
from concurrent.futures import ProcessPoolExecutor, as_completed
def reciprocal(value: int) -> float:
return 1 / value
if __name__ == "__main__":
with ProcessPoolExecutor() as pool:
pending = {pool.submit(reciprocal, value): value for value in [2, 0]}
for future in as_completed(pending):
value = pending[future]
try:
print(value, future.result())
except ZeroDivisionError:
print(value, "failed")
Completion order can vary, so this example intentionally does not claim one exact output order.
Windows uses spawn. Python 3.14 changed the POSIX default away from fork; fork is no longer the default on any platform. Libraries should not force one global method without documenting the requirement and allowing the application to provide a context.
0 of 2 checked
Try this next
0 of 2 completed
Explore 500+ free tutorials across 20+ languages and frameworks.