How to Use ProcessPoolExecutor for CPU Parallel Map in Python

Run a function over a sequence of inputs in parallel across multiple CPU cores with ProcessPoolExecutor.map.

Medium Python 3.9+ Aug 9, 2026 Concurrency & performance 11 views 0 copies

Python code

25 lines
Python 3.9+
from concurrent.futures import ProcessPoolExecutor
import math

def compute_square(num):
    return num * num

def is_prime(n):
    if n < 2:
        return False
    for i in range(2, int(math.sqrt(n)) + 1):
        if n % i == 0:
            return False
    return True

if __name__ == "__main__":
    numbers = range(1, 11)
    
    with ProcessPoolExecutor() as executor:
        squares = list(executor.map(compute_square, numbers))
    
    with ProcessPoolExecutor() as executor:
        primes = list(executor.map(is_prime, range(2, 21)))
    
    print("Squares:", squares)
    print("Prime check:", primes)

Output

stdout
Squares: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
Prime check: [False, True, True, False, True, False, True, False, False, False, True, False, True, False, False, False, True, False, True, False]

How it works

ProcessPoolExecutor.map distributes each input item to a worker process, running the callable in parallel across CPU cores. The if __name__ == "__main__" guard is mandatory so worker processes on Windows and macOS can safely import the script. Each with block waits for all tasks to finish and collects results in input order before exiting. Use this for CPU-bound tasks like arithmetic or prime checks, where the GIL would otherwise serialize threads. Results come back as a list when you wrap the map in list(), preserving the original order of the inputs.

Common mistakes

  • Forgetting the `if __name__ == "__main__"` guard, which causes infinite recursion or crashes on Windows/macOS.
  • Using ProcessPoolExecutor for I/O-bound tasks where asyncio or ThreadPoolExecutor would be much faster.
  • Calling `executor.map` but never consuming the iterator, so tasks never start.
  • Sharing large picklable objects between workers via closures instead of passing them through arguments, slowing marshalling.

Variations

  1. Use `executor.submit` with `as_completed` when you need to handle results as they finish instead of in order.
  2. Raise the worker count with `ProcessPoolExecutor(max_workers=os.cpu_count() - 1)` to reserve a core for the main process.

Real-world use cases

  • Batch-resizing or filtering thousands of images in a web service without blocking the event loop.
  • Running cryptographic hash rounds or checksum verification across log files in a data pipeline.
  • Parallelizing computational geometry or simulation sweeps on a reporting server with idle cores.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Concurrency & performance

Related tutorials and quizzes for this topic.