How to Use Concurrent Futures in Python for Easy Parallelism
Use Python's
concurrent.futures module to run tasks concurrently by creating a ThreadPoolExecutor or ProcessPoolExecutor. Submit functions to the executor with submit() or use map() for simpler parallel execution, then collect results with Future objects.Syntax
The concurrent.futures module provides two main executors: ThreadPoolExecutor for threading and ProcessPoolExecutor for multiprocessing. You create an executor, submit tasks, and get Future objects representing the running tasks.
Key parts:
with ThreadPoolExecutor(max_workers=n) as executor:- creates a thread pool withnthreads.executor.submit(func, *args)- schedulesfuncto run with argumentsargsand returns aFuture.future.result()- waits for the task to finish and returns its result.executor.map(func, iterable)- runsfuncon each item initerableconcurrently, returning results in order.
python
from concurrent.futures import ThreadPoolExecutor def task(x): return x * 2 with ThreadPoolExecutor(max_workers=3) as executor: future = executor.submit(task, 5) result = future.result() print(result) # Output: 10
Output
10
Example
This example shows how to use ThreadPoolExecutor to run a simple function concurrently on multiple inputs and collect the results.
python
from concurrent.futures import ThreadPoolExecutor import time def square(n): time.sleep(1) # Simulate a delay return n * n inputs = [1, 2, 3, 4, 5] with ThreadPoolExecutor(max_workers=3) as executor: results = list(executor.map(square, inputs)) print(results)
Output
[1, 4, 9, 16, 25]
Common Pitfalls
Common mistakes when using concurrent.futures include:
- Not calling
result()onFutureobjects, so you never get the output or exceptions. - Using
ThreadPoolExecutorfor CPU-heavy tasks, which may not speed up due to Python's Global Interpreter Lock (GIL); useProcessPoolExecutorinstead. - Not properly shutting down the executor, which can be avoided by using the
withstatement.
Example of wrong and right usage:
python
from concurrent.futures import ThreadPoolExecutor def task(x): return x + 1 # Wrong: forgetting to get result with ThreadPoolExecutor() as executor: future = executor.submit(task, 10) # Missing future.result() means no output or error handling # Right: getting the result with ThreadPoolExecutor() as executor: future = executor.submit(task, 10) print(future.result()) # Output: 11
Output
11
Quick Reference
| Method | Description |
|---|---|
| ThreadPoolExecutor(max_workers=n) | Creates a pool of n threads for I/O-bound tasks. |
| ProcessPoolExecutor(max_workers=n) | Creates a pool of n processes for CPU-bound tasks. |
| executor.submit(func, *args) | Schedules func to run with args, returns a Future. |
| future.result() | Waits for the task to finish and returns the result. |
| executor.map(func, iterable) | Runs func on each item in iterable concurrently, returns results in order. |
Key Takeaways
Use ThreadPoolExecutor for I/O-bound tasks and ProcessPoolExecutor for CPU-bound tasks.
Always call result() on Future objects to get results or catch exceptions.
Use the with statement to ensure executors shut down properly.
executor.map() is a simple way to run a function on many inputs concurrently.
Avoid blocking the main thread by running long tasks inside executors.