Futures and executors
Submit work, retrieve results, and propagate failures
Submitting work
An executor owns a worker pool. submit() schedules a callable and returns a Future representing pending, running, completed, or cancelled work.
future.result() waits if needed, then returns the value or re-raises the callable's exception in the requesting thread. A timeout limits the wait, not the operation's lifetime:
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=2) as executor:
valid = executor.submit(int, "42")
invalid = executor.submit(int, "many")
print(valid.result()) # 42
try:
invalid.result()
except ValueError:
print("conversion failed")Explicit retrieval determines display order; tasks may finish in either order. Calling result() again observes the stored outcome without resubmitting. Exiting the executor's context waits for submitted work.
Ordering and cancellation
executor.map yields results in input order; as_completed exposes completion order. Ordered retrieval can hold a fast result behind a slow earlier item. Retain indices when collecting by completion but presenting by input order.
Cancellation can prevent pending work from starting; it cannot stop a running callable. Events make this distinction deterministic:
from concurrent.futures import ThreadPoolExecutor
from threading import Event
entered, release = Event(), Event()
def wait_for_release():
entered.set()
release.wait()
return "done"
with ThreadPoolExecutor(max_workers=1) as executor:
active = executor.submit(wait_for_release)
try:
entered.wait()
queued = executor.submit(str, "never run")
print(queued.cancel(), active.cancel()) # True False
finally:
release.set()
print(active.result()) # doneThe first call occupies the sole worker, leaving the second pending. Releasing the first uses a separate signal; cancellation did not interrupt it. Running calls need cooperative stop checks and appropriate timeouts on external operations.
Capacity and failure policy
Thread pools overlap blocking I/O; process pools provide isolation for suitable CPU work. Set worker capacity according to downstream limits and measurement.
Worker count does not bound pending-submission memory. Older Executor.map APIs collect inputs eagerly; submission controls vary by version. Use a window of futures or a bounded producer-consumer queue when pending work must be bounded.
Gotchas
- One task failing does not cancel its siblings automatically. Define whether to collect item failures or fail the batch.
- Waiting inside every worker for more work on the same exhausted pool can deadlock.
- Context-manager shutdown can wait for slow running calls even after the body raises.
- An executor neither synchronizes application state nor undoes side effects.
Native thread pools are unavailable in the browser runner.
Put it into practice
Implement the exercise in the workspace, then download it and run the tests with your local toolchain.