0
0
PythonHow-ToBeginner · 4 min read

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 with n threads.
  • executor.submit(func, *args) - schedules func to run with arguments args and returns a Future.
  • future.result() - waits for the task to finish and returns its result.
  • executor.map(func, iterable) - runs func on each item in iterable concurrently, 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() on Future objects, so you never get the output or exceptions.
  • Using ThreadPoolExecutor for CPU-heavy tasks, which may not speed up due to Python's Global Interpreter Lock (GIL); use ProcessPoolExecutor instead.
  • Not properly shutting down the executor, which can be avoided by using the with statement.

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

MethodDescription
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.