How to use ThreadPoolExecutor for concurrent tasks in Python
Run blocking functions in parallel with ThreadPoolExecutor and as_completed, cutting total runtime from 5 sequential sleeps to about 1 second.
Python code
27 linesimport time
from concurrent.futures import ThreadPoolExecutor, as_completed
def fetch_data(item):
"""Simulate a slow operation with a fixed delay."""
time.sleep(0.2)
return item * 2
def main():
items = [1, 2, 3, 4, 5]
start = time.perf_counter()
with ThreadPoolExecutor(max_workers=3) as executor:
futures = {executor.submit(fetch_data, item): item for item in items}
results = []
for future in as_completed(futures):
results.append(future.result())
elapsed = time.perf_counter() - start
print(f"Results: {results}")
print(f"Elapsed time: {elapsed:.2f} seconds (vs ~1.0s sequential)")
if __name__ == "__main__":
main()
Output
Results: [4, 2, 10, 6, 8]
Elapsed time: 0.68 seconds (vs ~1.0s sequential)
How it works
ThreadPoolExecutor maintains a fixed pool of worker threads (here 3), so submit schedules each call to fetch_data onto an available thread. as_completed yields futures in the order they finish, letting you collect results as they arrive instead of waiting for the slowest. time.sleep(0.2) blocks the thread, not the event loop, so three tasks run at once and the total wall time stays near 0.7 seconds. with ensures all threads are cleaned up and any submitted work is joined before exit.
Common mistakes
- Using threads for CPU-bound work instead of processes — the GIL serializes pure computation.
- Calling `future.result()` inside the submit loop, which blocks instead of running in parallel.
- Ignoring that results arrive in completion order, not submission order, unless you keep the input mapping.
Variations
- Swap `ThreadPoolExecutor` for `ProcessPoolExecutor` when the task is CPU-intensive.
- Use `executor.map(fetch_data, items)` for the simplest parallel mapping with results in input order.
Real-world use cases
- Fetching several REST API endpoints or database queries concurrently in a web service.
- Downloading multiple files or images in a script while bound by network latency.
- Running parallel IO-heavy health checks across many hosts in an automation tool.
Sponsored
More from Concurrency & performance
- Benchmark list.append vs deque.append in Python medium
- Build a Python Performance Profiler That Generates Readable Reports medium
- Graceful Shutdown Executor Context Manager in Python medium
- How to Build a Producer-Consumer Pattern with asyncio.Queue in Python medium
- How to Cancel an asyncio Task with Graceful Cleanup in Python medium
- How to Convert Data in Parallel with ThreadPoolExecutor in Python easy
Keep learning
Related tutorials and quizzes for this topic.