concurrent.futures
concurrent.futures provides high-level Executor pools with Future objects for async results. ThreadPoolExecutor overlaps I/O; ProcessPoolExecutor parallelizes CPU work.
Search across all documentation pages
concurrent.futures provides high-level Executor pools with Future objects for async results. ThreadPoolExecutor overlaps I/O; ProcessPoolExecutor parallelizes CPU work.
from concurrent.futures import ThreadPoolExecutor, as_completed
def task(n: int) -> int:
return n + 1
with ThreadPoolExecutor(max_workers=4) as ex:
futures = [ex.submit(task, i) for i in range(10)]
for fut in as_completed(futures):
print(fut.result())When to reach for this:
map with timeout and chunkingasyncio.wrap_futureimport time
from concurrent.futures import ProcessPoolExecutor, wait, FIRST_COMPLETED
def slow_square(n: int) -> int:
time.sleep(0.05)
return n * n
if __name__ == "__main__":
with ProcessPoolExecutor(max_workers=2) as ex:
futures = [ex.submit(slow_square, i) for i in range(6)]
done, not_done = wait(futures, return_when=FIRST_COMPLETED)
print("first", done.pop().result())
for fut in not_done:
fut.cancel()What this demonstrates:
wait with FIRST_COMPLETED for partial resultscancel best-effort on not-yet-running tasks| Call | Behavior |
|---|---|
submit(fn, *args) | Single Future |
map(fn, iterable) | Iterator ordered like input |
as_completed(futures) | Yield as each finishes |
future.result(timeout=) | Block or raise TimeoutError |
result() without timeout - hangs shutdown. Fix: timeouts + cancellation policy.result(). Fix: handle or log per future.| Alternative | Use When | Don't Use When |
|---|---|---|
| asyncio.gather | Native async I/O | Blocking SDK |
| multiprocessing.Pool | Legacy code | Greenfield prefers futures |
| joblib / dask | Big data parallelism | Simple scripts |
Threads for I/O blocking; processes for CPU-bound Python functions.
map preserves order and batches; submit gives fine-grained per-task control.
Raised when calling result() on the failed Future.
Returns immediately; running tasks continue in background - use carefully on exit.
asyncio.wrap_future bridges to awaitable in mixed code.
min(32, cpu_count + 4) for threads - tune per workload.
Yes on both executor types for per-worker setup (DB connections - mind process safety).
add_done_callback runs in arbitrary thread - keep callbacks short.
Inject executor with max_workers=1 for determinism in unit tests.
Each process has own GIL - true parallel bytecode execution.
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.
Reviewed by Chris St. John·Last updated Jul 19, 2026