You can shutdown and close the ThreadPoolExecutor by calling the shutdown() method.
You can also shutdown the ThreadPoolExecutor safely and automatically using the context manager interface or by allowing the Python interpreter to close the pool for you when exiting the Python interpreter.
In this tutorial, you will discover how to shutdown the ThreadPoolExecutor in Python.
Let’s get started.
How to Shutdown the ThreadPoolExecutor
There are three ways to shut down the ThreadPoolExecutor, they are:
- Manually via the shutdown() method.
- Automatically via the context manager interface.
- Automatically when the Python interpreter is exited.
Let’s take a closer look at each in turn.
Shutdown the ThreadPoolExecutor Manually
We can shut down the ThreadPoolExecutor manually by calling the shutdown() method.
This method takes two arguments:
- wait (defaults to True)
- cancel_futures (defaults to False)
The default behavior of the shutdown() method is to block the calling thread until all threads in the pool have been released. Any tasks issued to the pool that have not started will not be canceled. Therefore, all tasks issued to the pool, whether executing or not, will complete before the call to shutdown() returns.
For example:
1 2 3 |
... # shutdown the thread pool tpe.shutdown() |
This is the same as:
1 2 3 |
... # shutdown the thread pool tpe.shutdown(wait=True, cancel_futures=False) |
The “wait” argument indicates whether the calling thread will block or not until the worker thread has been terminated.
- wait=True, (default) block until all executing tasks are complete and all resources have been released.
- wait=False, do not block.
For example:
1 2 3 |
... # shutdown the thread pool and do not block tpe.shutdown(wait=False) |
The “cancel_futures” argument indicates whether the issued tasks that are not yet executed will be canceled (via their cancel()) method or not.
- cancel_futures=True, call cancel() on each issued task that is not executing.
- cancel_futures=False, (default) do not cancel issued tasks that are not executing.
For example:
1 2 3 |
... # shutdown the thread pool and cancel issued tasks that are not executing tpe.shutdown(cancel_futures=True) |
Recall that we cannot cancel tasks that are executing.
You can learn more about this in the tutorial:
Shutdown Automatically via Context Manager
We can shut down the ThreadPoolExecutor automatically via the context manager interface.
When the block within the context manager is exited either normally or via an exception, the shutdown() method is called automatically for us with default arguments (wait=True, cancel_futures=False).
For example:
1 2 3 4 5 6 |
... # create a thread pool via the context manager interface with ThreadPoolExecutor() as tpe: # use the pool # ... # pool is closed automatically |
This means that if there are tasks executing in the pool, then exiting the context manager will block the current thread until all tasks are done and the resources for the ThreadPoolExecutor have been released.
You can learn more about the context manager interface in the tutorial:
Shutdown Automatically By Exiting The Interpreter
We can shut down the ThreadPoolExecutor automatically by exiting the Python interpreter, e.g. closing the program.
This may be achieved in many ways, such as terminating the program externally, such as via the “kill” command, or within the program by calling the sys.exit() method.
It may also be achieved by creating and using a ThreadPoolExecutor and not explicitly or implicitly shutting it down and allowing the program to exit normally or via an unhandled exception.
For example:
1 2 3 4 5 6 |
... # create a thread pool tpe = ThreadPoolExecutor() # issue tasks # ... # do not shutdown the pool... |
The Python interpreter will try to close the ThreadPoolExecutor cleanly.
It will join the worker threads in the ThreadPoolExecutor and allow the tasks to end normally.
All threads enqueued to ThreadPoolExecutor will be joined before the interpreter can exit. Note that the exit handler which does this is executed before any exit handlers added using atexit. This means exceptions in the main thread must be caught and handled in order to signal threads to exit gracefully. For this reason, it is recommended that ThreadPoolExecutor not be used for long-running tasks.
— concurrent.futures – Launching parallel tasks
This means that threads running in the ThreadPoolExecutor will block a program from exiting until the tasks executing in the worker threads are complete.
What If Tasks Are Issued After Shutdown?
It is not possible to issue tasks to the ThreadPoolExecutor after it has shut down.
The call to submit() or map() methods on the ThreadPoolExecutor will fail immediately with an Exception and the message:
- cannot schedule new futures after shutdown
This can lead to unexpected Exceptions when issuing subtasks from tasks and using the context manager interface.
You can see an example of this in the tutorial:
Now that we know how to shut down the ThreadPoolExecutor, let’s look at some worked examples.
Run loops using all CPUs, download your FREE book to learn how.
Example of Manual Shutdown of the ThreadPoolExecutor
We can explore cases of manually shutting down the ThreadPoolExecutor via the shutdown() method.
Given the two boolean arguments for the shutdown() method, there are four cases we can explore, they are:
- Shutdown, wait for tasks, don’t cancel tasks (default).
- Shutdown, don’t wait for tasks, don’t cancel tasks.
- Shutdown, wait for tasks, cancel tasks.
- Shutdown, don’t wait for tasks, cancel tasks.
Let’s explore each case in turn
Shutdown, Wait For Tasks to Complete, Don’t Cancel Tasks
In this example, we will create a thread pool, issue one task that takes 2 seconds to complete, then shut down the thread pool.
Shutting down the pool will wait for the running task to complete and will not cancel any scheduled tasks.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# SuperFastPython.com # example of threadpoolexecutor shutdown, wait for tasks and don't cancel from concurrent.futures import ThreadPoolExecutor from time import sleep # create a thread pool tpe = ThreadPoolExecutor() # issue some tasks _ = tpe.submit(sleep, 2) # report a message print('Tasks have been issued, shutting down') # shutdown the thread pool tpe.shutdown(wait=True, cancel_futures=False) print('Done') |
Running the example first creates the thread pool.
A sleep task is then issued into the pool, which executes immediately and lasts two seconds.
A message is reported and the thread pool is shut down. This call blocks for two seconds waiting for the executing task to complete.
Finally, a done message is reported.
This example highlights the default behavior of the shutdown() method that will wait for all issued and executing tasks to complete.
1 2 |
Tasks have been issued, shutting down Done |
Shutdown, Don’t Wait For Tasks to Complete, Don’t Cancel Tasks
In this example, we will create a thread pool, issue one task that takes 2 seconds to complete, then shut down the thread pool.
Shutting down the pool will not wait for the running task to complete and will not cancel any scheduled tasks.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# SuperFastPython.com # example of threadpoolexecutor shutdown, don't wait for tasks and don't cancel from concurrent.futures import ThreadPoolExecutor from time import sleep # create a thread pool tpe = ThreadPoolExecutor() # issue some tasks _ = tpe.submit(sleep, 2) # report a message print('Tasks have been issued, shutting down') # shutdown the thread pool tpe.shutdown(wait=False, cancel_futures=False) print('Done') |
Running the example first creates the thread pool.
A sleep task is then issued into the pool, which executes immediately and lasts two seconds.
A message is reported and the thread pool is shut down. This call does not block.
Finally, a done message is reported immediately.
The program then ends and the Python interpreter itself blocks and waits for all threads in the thread pool to complete.
This example highlights how we can request the thread pool to shut down without waiting for all tasks to complete, allowing the program to go on and perform other activities.
1 2 |
Tasks have been issued, shutting down Done |
Shutdown, Wait For Tasks to Complete, Cancel Tasks
In this example, we first create a thread pool with the capacity to execute a single task.
Then three tasks are issued, each taking one second to complete.
The pool is then shut down, waiting for executing tasks to complete but canceling scheduled tasks.
The cancel status of all three issued tasks is then reported.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# SuperFastPython.com # example of threadpoolexecutor shutdown, wait for tasks and cancel from concurrent.futures import ThreadPoolExecutor from time import sleep # create a thread pool tpe = ThreadPoolExecutor(1) # issue some tasks f1 = tpe.submit(sleep, 1) f2 = tpe.submit(sleep, 1) f3 = tpe.submit(sleep, 1) # report a message print('Tasks have been issued, shutting down') tpe.shutdown(wait=True, cancel_futures=True) print('Done') # report status of tasks print(f'Task 1 cancelled: {f1.cancelled()}') print(f'Task 2 cancelled: {f2.cancelled()}') print(f'Task 3 cancelled: {f3.cancelled()}') |
Running the example first creates the thread pool with the capacity to execute one task at a time.
Three sleep tasks are issued to the pool, to be executed sequentially.
A message is reported and the pool is shut down. This call blocks until the first task is done. The remaining two tasks are canceled.
A “done” message is reported, then the cancel status of all three tasks is reported. We can see that the first task was not canceled and the remaining two tasks were canceled, as we expect.
This highlights how we can shut down and block waiting for executing tasks to complete, but not allow new scheduled tasks to begin executing.
1 2 3 4 5 |
Tasks have been issued, shutting down Done Task 1 cancelled: False Task 2 cancelled: True Task 3 cancelled: True |
Shutdown, Don’t Wait For Tasks to Complete, Cancel Tasks
In this example, we first create a thread pool with the capacity to execute a single task.
Then three tasks are issued, each taking one second to complete.
The pool is then shut down, without waiting and canceling any scheduled tasks.
The cancel status of all three issued tasks is then reported.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# SuperFastPython.com # example of threadpoolexecutor shutdown, don't wait for tasks and cancel from concurrent.futures import ThreadPoolExecutor from time import sleep # create a thread pool tpe = ThreadPoolExecutor(1) # issue some tasks f1 = tpe.submit(sleep, 1) f2 = tpe.submit(sleep, 1) f3 = tpe.submit(sleep, 1) # report a message print('Tasks have been issued, shutting down') # shutdown the thread pool tpe.shutdown(wait=False, cancel_futures=True) print('Done') # report status of tasks print(f'Task 1 cancelled: {f1.cancelled()}') print(f'Task 2 cancelled: {f2.cancelled()}') print(f'Task 3 cancelled: {f3.cancelled()}') |
Running the example first creates the thread pool with the capacity to execute one task at a time.
Three sleep tasks are issued to the pool, to be executed sequentially.
A message is reported and the pool is shut down. This call does not block, instead, it returns immediately. The remaining two tasks are canceled.
A “done” message is reported, then the cancel status of all three tasks is reported. We can see that the first task was not canceled and the remaining two tasks were canceled, as we expect.
The program then blocks while the interpreter waits for the single executing task to complete.
This highlights how we can shut down the thread pool and cancel all scheduled tasks without blocking, allowing the program to go on with other activities while the worker threads finish and are released.
1 2 3 4 5 |
Tasks have been issued, shutting down Done Task 1 cancelled: False Task 2 cancelled: True Task 3 cancelled: True |
Example of Automatic Shutdown via Context Manager
We can explore the case of automatically shutting down the ThreadPoolExecutor via the context manager.
This involves creating and using the ThreadPoolExecutor via the context manager interface and then not explicitly shutting down the pool.
Instead, the pool is shut down automatically as soon as we leave the context manager block.
This calls the shutdown() method for us with the default arguments, ensuring that executing and scheduled tasks are all complete before resuming.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 |
# SuperFastPython.com # example of threadpoolexecutor shutdown, via context manager from concurrent.futures import ThreadPoolExecutor from time import sleep # create a thread pool with ThreadPoolExecutor() as tpe: # issue some tasks _ = tpe.submit(sleep, 1) # report a message print('Tasks have been issued, shutting down') print('Done') |
Running the example first creates the ThreadPoolExecutor using the context manager interface.
A single sleep task is issued that runs for one second.
A message is reported and then the context manager is exited.
This blocks the main thread until the issued task is completed.
Finally, the “done” message is reported.
This highlights how we can use the ThreadPoolExecutor and allow the context manager interface to shut down the pool for us once we are done with it.
1 2 |
Tasks have been issued, shutting down Done |
Free Python ThreadPoolExecutor Course
Download your FREE ThreadPoolExecutor PDF cheat sheet and get BONUS access to my free 7-day crash course on the ThreadPoolExecutor API.
Discover how to use the ThreadPoolExecutor class including how to configure the number of workers and how to execute tasks asynchronously.
Example of Automatic Shutdown via Python Interpreter
The ThreadPoolExecutor can be shut down automatically for us by the Python interpreter when we attempt to exit our program.
We will explore three examples of this use case, they are:
- Shut down when exiting the program normally.
- Shut down when exiting the program via an unhandled exception.
- Shut down when exiting with an explicit call to sys.exit().
In all three cases, the Python interpreter will block by joining the threads of the ThreadPoolExecutor until the tasks are complete, preventing the main thread from exiting.
Let’s dive in.
Shutdown Via Exiting Interpreter Normally
We can shut down the ThreadPoolExecutor automatically via the Python interpreter when exiting normally.
In this example, we will create a thread pool and then issue a blocking task. We will then report a message and attempt to exit the program.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 |
# SuperFastPython.com # example of threadpoolexecutor shutdown, shutdown via interpreter from concurrent.futures import ThreadPoolExecutor from time import sleep # create a thread pool tpe = ThreadPoolExecutor() # issue some tasks _ = tpe.submit(sleep, 2) # report a message print('Tasks have been issued, not shutting down...') |
Running the example first creates a ThreadPoolExecutor.
Next, a single sleep task is issued to the thread pool that blocks for two seconds.
Next, a message is reported and the main thread attempts to exit.
The program then blocks until the single issued task is complete.
This highlights that Python will wait for tasks to complete before exiting the program, effectively shutting down the ThreadPoolExecutor automatically.
1 |
Tasks have been issued, not shutting down... |
Shutdown Via Exiting Interpreter With Exception
We can explore the case of the Python interpreter shutting down the ThreadPoolExecutor automatically in the case where the main thread fails with an exception.
The example below updates the previous example to raise an unhandled exception as the last statement in the program.
This forces the interpreter to handle the termination of the main thread and program with an error condition.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 |
# SuperFastPython.com # example of threadpoolexecutor shutdown, shutdown via interpreter and exception from concurrent.futures import ThreadPoolExecutor from time import sleep # create a thread pool tpe = ThreadPoolExecutor() # issue some tasks _ = tpe.submit(sleep, 2) # report a message print('Tasks have been issued, raising an exception...') raise Exception('something bad happened') |
Running the example first creates a ThreadPoolExecutor.
Next, a single sleep task is issued to the thread pool that blocks for two seconds.
Next, a message is reported.
Finally, an exception is raised that is not handled.
The program then blocks until the single issued task is complete, then the exception stack trace and message is reported as per normal.
This highlights that Python will wait for tasks to be complete before exiting the program, even if the program exits via an unhandled exception.
1 2 3 4 5 |
Tasks have been issued, raising an exception... Traceback (most recent call last): ... raise Exception('something bad happened') Exception: something bad happened |
Shutdown Via Exiting Interpreter With sys.exit()
We can explore the case where the Python interpreter is explicitly terminated via a call to the sys.exit() function and the ThreadPoolExecutor is shut down automatically.
In this example, we update the above example so that the last line of the program is a call to sys.exit() that terminates the current program (instance of the Python interpreter).
You can learn more about sys.exit() in the tutorial:
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# SuperFastPython.com # example of threadpoolexecutor shutdown, shutdown via interpreter and sys.exit() from concurrent.futures import ThreadPoolExecutor from time import sleep import sys # create a thread pool tpe = ThreadPoolExecutor() # issue some tasks _ = tpe.submit(sleep, 2) # report a message print('Tasks have been issued, exiting...') # force exit sys.exit() |
Running the example first creates a ThreadPoolExecutor.
Next, a single sleep task is issued to the thread pool that blocks for two seconds.
Next, a message is reported.
Finally, a call is made to sys.exit() that explicitly terminates the processes.
The program then blocks until the single issued task is complete.
This highlights that Python will wait for tasks to be complete before exiting the program, even when the process is explicitly terminated.
1 |
Tasks have been issued, exiting... |
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
Example of Issuing Tasks After Shutdown
We cannot issue tasks to the ThreadPoolExecutor after it has been shut down.
Attempting to do so will result in an Exception.
There are two scenarios where we may attempt to issue tasks to a ThreadPoolExecutor that has already been shut down, they are:
- Issue tasks via a call to submit()
- Issue tasks via a call to map()
Both approaches will fail with the same exception.
Let’s explore both cases.
Issuing Tasks After Shutdown With submit()
We can explore the case where we issue a task using submit() to a ThreadPoolExecutor after it has been shut down.
In the example below, we create a ThreadPoolExecutor, issue a task, shut down normally, then attempt to issue a follow-up task by calling the submit() method.
We expect this will fail with an exception.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# SuperFastPython.com # example of threadpoolexecutor shutdown, issue tasks with submit() from concurrent.futures import ThreadPoolExecutor from time import sleep # create a thread pool tpe = ThreadPoolExecutor() # issue some tasks _ = tpe.submit(sleep, 2) # report a message print('Tasks have been issued, shutting down') # shutdown the thread pool tpe.shutdown(wait=True, cancel_futures=False) # attempt to issue a follow-up task print('Issuing a task after shutdown') _ = tpe.submit(sleep, 1) |
Running the example first creates a ThreadPoolExecutor.
A task is issued to the pool that blocks for two seconds.
The thread pool is then shut down, which blocks the main thread until the task is completed.
A message is reported, then a follow-up task is issued via the submit() method.
This fails with a RunTimeError exception and the message “cannot schedule new futures after shutdown“.
This highlights that we cannot issue tasks after the ThreadPoolExecutor has been shut down using the submit() method.
1 2 3 4 5 6 |
Tasks have been issued, shutting down Issuing a task after shutdown Traceback (most recent call last): ... raise RuntimeError('cannot schedule new futures after shutdown') RuntimeError: cannot schedule new futures after shutdown |
Issuing Tasks After Shutdown With map()
We cannot issue tasks to the ThreadPoolExecutor after it is shut down using the map() method.
Internally, the map() method calls the submit() method for each task, so we expect that a call to map() will fail in the same way with the same exception.
The complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# SuperFastPython.com # example of threadpoolexecutor shutdown, issue tasks with map() from concurrent.futures import ThreadPoolExecutor from time import sleep # create a thread pool tpe = ThreadPoolExecutor() # issue some tasks _ = tpe.map(sleep, [1, 1, 1]) # report a message print('Tasks have been issued, shutting down') # shutdown the thread pool tpe.shutdown(wait=True, cancel_futures=False) # attempt to issue a follow-up task print('Issuing a task after shutdown') _ = tpe.map(sleep, [1, 1, 1]) |
Running the example first creates a ThreadPoolExecutor.
A set of 3 tasks are then issued to the thread pool normally using the map() method.
The thread pool is then shut down, which blocks the main thread until the three tasks are completed.
A message is reported, then a set of three follow-up tasks are issued via the map() method.
This fails with a RunTimeError exception and the message “cannot schedule new futures after shutdown“.
This highlights that we cannot issue tasks after the ThreadPoolExecutor has been shut down using the map() method.
1 2 3 4 5 6 |
Tasks have been issued, shutting down Issuing a task after shutdown Traceback (most recent call last): ... raise RuntimeError('cannot schedule new futures after shutdown') RuntimeError: cannot schedule new futures after shutdown |
Further Reading
This section provides additional resources that you may find helpful.
Books
- ThreadPoolExecutor Jump-Start, Jason Brownlee, (my book!)
- Concurrent Futures API Interview Questions
- ThreadPoolExecutor Class API Cheat Sheet
I also recommend specific chapters from the following books:
- Effective Python, Brett Slatkin, 2019.
- See Chapter 7: Concurrency and Parallelism
- Python in a Nutshell, Alex Martelli, et al., 2017.
- See: Chapter: 14: Threads and Processes
Guides
- Python ThreadPoolExecutor: The Complete Guide
- Python ProcessPoolExecutor: The Complete Guide
- Python Threading: The Complete Guide
- Python ThreadPool: The Complete Guide
APIs
References
Takeaways
You now know how to shut down the ThreadPoolExecutor in Python.
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Photo by Dmitrii Vaccinium on Unsplash
Do you have any questions?