concurrent.futures: executors and futures

ThreadPoolExecutor, ProcessPoolExecutor, Future, map, as_completed, wait and shutdown: what each call returns, when it blocks, what its timeout does, and a test the build ran for each.

What is the default max_workers of ThreadPoolExecutor?

On Python 3.13 and later it is min(32, (os.process_cpu_count() or 1) + 4). From 3.8 to 3.12 it was min(32, os.cpu_count() + 4), and from 3.5 to 3.7 it was the number of processors times five. The documentation gives the reason for the plus four: it keeps at least five workers for I/O-bound work. In a round, pass max_workers explicitly and say what limits it, such as how many connections the server at the other end accepts.

Does future.result(timeout=...) raise concurrent.futures.TimeoutError or the built-in TimeoutError?

Both names are the same class on Python 3.11 and later, because 3.11 made concurrent.futures.TimeoutError an alias of the built-in. On 3.10 and earlier the documentation lists it as the module’s own exception, so code that has to run there should catch concurrent.futures.TimeoutError by that name.

Why does cancel() return False for a task I submitted a moment ago?

Because a worker had already picked it up. cancel() only works on a future that is still pending. Once a worker marks it running, cancel() returns False and the call runs to the end. The threading documentation says threads cannot be destroyed, stopped, suspended, resumed, or interrupted, so a task that should end early has to check a threading.Event itself.

Why does ProcessPoolExecutor fail with a lambda?

The pool sends the function to a worker process by pickling it, and pickle stores a function by its importable name, not its code. A lambda or a function defined inside another function has no name a worker can import, so pickling it raises PicklingError, and the future re-raises that error from result().

Should I use threads, processes or asyncio?

Threads for I/O-bound work in ordinary blocking code: the threading documentation calls them still an appropriate model for running several I/O-bound tasks at once. Processes for CPU-bound work in pure Python, because in the default CPython build the global interpreter lock lets only one thread execute Python bytecode at a time. asyncio for many concurrent network connections in code written with async and await. The table at the end of the page gives each choice its reason.