Introduction
Thread pools are a way to manage a group of threads for executing multiple tasks concurrently. Instead of creating a new thread for each task, a thread pool allows you to reuse a fixed number of threads. This can be more efficient and easier to manage. Python provides the concurrent.futures
module, which includes the ThreadPoolExecutor
class for managing thread pools.
Using ThreadPoolExecutor
The ThreadPoolExecutor
class provides a high-level interface for asynchronously executing callables.
Basic Usage
Example
import concurrent.futures
import time
def task(name, duration):
print(f"Task {name} starting")
time.sleep(duration)
print(f"Task {name} completed")
return f"Task {name} result"
# Create a ThreadPoolExecutor
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
# Submit tasks to the thread pool
futures = [executor.submit(task, f"Task-{i+1}", i+1) for i in range(5)]
# Wait for all tasks to complete and get results
for future in concurrent.futures.as_completed(futures):
print(future.result())
print("All tasks have finished execution.")
Output
Task Task-1 starting
Task Task-2 starting
Task Task-3 starting
Task Task-1 completed
Task Task-1 result
Task Task-4 starting
Task Task-2 completed
Task Task-2 result
Task Task-5 starting
Task Task-3 completed
Task Task-3 result
Task Task-4 completed
Task Task-4 result
Task Task-5 completed
Task Task-5 result
All tasks have finished execution.
Submitting Tasks with submit()
The submit()
method schedules the callable to be executed and returns a Future
object representing the execution of the callable.
Example
import concurrent.futures
def square(n):
return n * n
# Create a ThreadPoolExecutor
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
# Submit tasks to the thread pool
future1 = executor.submit(square, 5)
future2 = executor.submit(square, 10)
# Get results
result1 = future1.result()
result2 = future2.result()
print(f"Square of 5: {result1}")
print(f"Square of 10: {result2}")
Output
Square of 5: 25
Square of 10: 100
Using map() Method
The map()
method submits a callable to the thread pool for each item in the iterable and returns an iterator that yields the results.
Example
import concurrent.futures
def square(n):
return n * n
numbers = [1, 2, 3, 4, 5]
# Create a ThreadPoolExecutor
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
# Use map to execute tasks
results = executor.map(square, numbers)
# Print results
for number, result in zip(numbers, results):
print(f"Square of {number}: {result}")
Output
Square of 1: 1
Square of 2: 4
Square of 3: 9
Square of 4: 16
Square of 5: 25
Handling Exceptions
You can handle exceptions that occur during task execution by checking the Future
object’s exception()
method.
Example
import concurrent.futures
def divide(a, b):
return a / b
# Create a ThreadPoolExecutor
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
# Submit tasks to the thread pool
future1 = executor.submit(divide, 10, 2)
future2 = executor.submit(divide, 10, 0)
# Get results and handle exceptions
try:
result1 = future1.result()
print(f"10 / 2 = {result1}")
except Exception as e:
print(f"Exception occurred: {e}")
try:
result2 = future2.result()
print(f"10 / 0 = {result2}")
except Exception as e:
print(f"Exception occurred: {e}")
Output
10 / 2 = 5.0
Exception occurred: division by zero
Waiting for Tasks to Complete
You can use as_completed()
to wait for tasks to complete and retrieve their results as they become available.
Example
import concurrent.futures
import time
def task(name, duration):
print(f"Task {name} starting")
time.sleep(duration)
print(f"Task {name} completed")
return f"Task {name} result"
# Create a ThreadPoolExecutor
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
# Submit tasks to the thread pool
futures = [executor.submit(task, f"Task-{i+1}", i+1) for i in range(5)]
# Wait for tasks to complete and get results
for future in concurrent.futures.as_completed(futures):
print(future.result())
print("All tasks have finished execution.")
Output
Task Task-1 starting
Task Task-2 starting
Task Task-3 starting
Task Task-1 completed
Task Task-1 result
Task Task-4 starting
Task Task-2 completed
Task Task-2 result
Task Task-5 starting
Task Task-3 completed
Task Task-3 result
Task Task-4 completed
Task Task-4 result
Task Task-5 completed
Task Task-5 result
All tasks have finished execution.
Conclusion
Thread pools in Python, managed by the ThreadPoolExecutor
class in the concurrent.futures
module, provide an efficient way to execute multiple tasks concurrently. By reusing a fixed number of threads, thread pools reduce the overhead of thread creation and destruction, making your programs more efficient and easier to manage. Understanding how to use submit()
, map()
, and handle exceptions with thread pools is essential for building robust and performant multithreaded applications in Python.