You can kill running tasks in the ThreadPoolExecutor by running the ThreadPoolExecutor in a new child process and calling the terminate() or kill() method on the child process.
This will abruptly and immediately terminate the child process and terminate all worker threads running in the ThreadPoolExecutor.
In this tutorial, you will discover how to kill running tasks in the ThreadPoolExecutor.
Let’s get started.
shutdown() Won’t Stop Running Tasks in the ThreadPoolExecutor
The ThreadPoolExecutor provides a pool of reusable worker threads using the executor design pattern.
Tasks executed in new threads are executed concurrently in Python, making the ThreadPoolExecutor appropriate for I/O-bound tasks.
A ThreadPoolExecutor can be created directly or via the context manager interface and tasks can be issued one-by-one via the submit() method or in batch via the map() method.
For example:
1 2 3 4 5 6 7 |
... # create a thread pool with ThreadPoolExecutor() as tpe: # issue a task future = type.submit(task) # the the task result once the task is done result = future.result() |
You can learn more about the ThreadPoolExecutor in the tutorial:
The ThreadPoolExecutor can be shut down by calling the shutdown() method.
For example:
1 2 3 |
... # shutdown the thread pool tpe.shutdown() |
This call can be configured to block until all tasks are done or return immediately and cancel all pending tasks or not.
You can learn more about how to shut down the ThreadPoolExecutor in the tutorial:
Shutting down the ThreadPoolExecutor will not terminate running tasks.
It is possible to update tasks to check a shared variable and terminate of their own accord. You can see an example of this in the tutorial:
Nevertheless, we cannot immediately terminate tasks running in the ThreadPoolExecutor.
Even if the program exists, the Python interpreter will wait for all threads in the ThreadPoolExecutor to complete and be released before the program terminates.
How can we forcefully terminate all running tasks in the ThreadPoolExecutor?
Run loops using all CPUs, download your FREE book to learn how.
How to Kill All Running Tasks in the ThreadPoolExecutor
There are a few ways that we may implement the forceful termination of worker threads in the ThreadPoolExecutor.
An approach I prefer is to host the ThreadPoolExecutor in a separate child process, then terminate the entire process in order to forcefully stop all running tasks.
For example, we can define a new function that creates and manages the ThreadPoolExecutor.
1 2 3 4 5 6 |
# function for managing a new thread pool def tpe_runner(): # create the thread pool with ThreadPoolExecutor(2) as tpe: # issue tasks # ... |
We can then create a new multiprocessing.Process instance to run the function in a child process.
For example:
1 2 3 4 |
... # start a child process to run the thread pool process = Process(target=tpe_runner) process.start() |
You can learn more about running functions in a child process in the tutorial:
Finally, when we want to stop all tasks immediately, we can call the terminate() method on the process.
For example:
1 2 3 |
... # stop all threads in the thread pool process.terminate() |
You can learn more about terminating and killing child processes in the tutorial:
This is a clean solution in that it does not require that the ThreadPoolExecutor class or the Future class be customized with workers or tasks that can be killed.
Instead, it provides a single point at which all workers and all helper threads in the ThreadPoolExecutor can be killed immediately.
The downside is that the ThreadPoolExecutor is not recoverable. It is terminated along with the workers. It also has the downside that any data needed by workers or any results produced by workers must be transferred from to or from the new child process, which can be costly if there is a lot of data involved.
Another downside is that tasks may be terminated mid-operation. This means that data or program state may be left inconsistent.
Now that we know how to forcefully terminate all workers in the ThreadPoolExecutor, let’s look at a worked example.
Example of Killing All Running Tasks in the ThreadPoolExecutor
We can develop an example that explores how to forcefully stop all worker threads in the ThreadPoolExecutor.
In this example, we will define a simple task to execute many times in our thread pool. We will then define a function that will be used to manage the thread pool in a new process. Finally, the main process will create a new child process to manage the thread pool, wait a while, then terminate the child process along with all running worker threads.
Firstly, we can define a task to execute in the thread pool.
The task tasks a unique integer argument sleeps for a moment to simulate doing work, then reports a message with its unique identifier.
These print messages will give us some idea that tasks are running and being completed in the thread pool.
The task() function below implements this.
1 2 3 4 5 6 |
# task executed in a thread pool def task(identifier): # block for a moment to simulate effort sleep(1) # report a message print(f'Task {identifier} done.', flush=True) |
Next, we can define a function to manage the ThreadPoolExecutor and issue all tasks, which will be executed in a child process.
The function will report a message at the beginning and end so we have some idea if the function is running and finished normally.
A ThreadPoolExecutor is created normally using the context manager interface with two worker threads. Then 10 task() calls are issued to the pool with unique arguments from 0 to 9.
The context manager interface ensures that the pool is closed automatically if the block is exited, blocking until all tasks are completed.
The tpe_runner() function below implements this.
1 2 3 4 5 6 7 8 9 10 |
# function for managing a new thread pool def tpe_runner(): # report a message print('TPE Runner has started.', flush=True) # create the thread pool with ThreadPoolExecutor(2) as tpe: # issue a bunch of tasks _ = tpe.map(task, range(10)) # block and wait for tasks to be done print('TPE Runner is done.', flush=True) |
Finally, we can develop the code in the main process.
First, we will create a new process to run our tpe_runner() function, then start this process.
1 2 3 4 |
... # start a child process to run the thread pool process = Process(target=tpe_runner) process.start() |
Next, the main process will block moments to allow some tasks to run and complete.
1 2 3 4 |
... # wait a while print('Main thread is waiting a moment...') sleep(3) |
Finally, the main process terminates the child process running the ThreadPoolExecutor.
1 2 3 4 5 |
... # main is terminating the tpe process print('Main is terminating the tpe...') process.terminate() print('Main is done.') |
Tying this together, 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 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# SuperFastPython.com # example of killing a threadpoolexecutor from multiprocessing import Process from concurrent.futures import ThreadPoolExecutor from time import sleep # task executed in a thread pool def task(identifier): # block for a moment to simulate effort sleep(1) # report a message print(f'Task {identifier} done.', flush=True) # function for managing a new thread pool def tpe_runner(): # report a message print('TPE Runner has started.', flush=True) # create the thread pool with ThreadPoolExecutor(2) as tpe: # issue a bunch of tasks _ = tpe.map(task, range(10)) # block and wait for tasks to be done print('TPE Runner is done.', flush=True) # protect the entry point if __name__ == '__main__': # start a child process to run the thread pool process = Process(target=tpe_runner) process.start() # wait a while print('Main thread is waiting a moment...') sleep(3) # main is terminating the tpe process print('Main is terminating the tpe...') process.terminate() print('Main is done.') |
Running the example first creates a new child process to run our tpe_runner() function. Then the new child process is started.
Next, the main process reports a message and blocks with a sleep.
The child process runs, reports a message then creates the ThreadPoolExecutor. All 10 task() calls are issued to the ThreadPoolExecutor and the child process until all tasks are complete.
The two worker threads in the ThreadPoolExecutor run and execute task() functions with unique arguments.
Two tasks are completed, then two more.
The main thread resumes and terminates the child process.
This immediately stops the child process and in turn, it terminates the ThreadPoolExecutor. This includes the termination of the two running worker threads and any helper threads internal to the ThreadPoolExecutor.
A final message is reported and the program terminates, again, not waiting for the workers in the child process to terminate.
This highlights how we can immediately terminate running tasks in the ThreadPoolExecutor.
1 2 3 4 5 6 7 8 |
Main thread is waiting a moment... TPE Runner has started. Task 0 done. Task 1 done. Task 2 done. Task 3 done. Main is terminating the tpe... Main is 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.
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
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
Takeaways
You now know how to kill running tasks in the ThreadPoolExecutor.
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Photo by Anton Savinov on Unsplash
Federer Fanatic says
hi, do you teach in person courses? Say at a local University?
Jason Brownlee says
Sorry, I don’t teach a course at this stage.
Email me and let me know what you need help with: Jason@SuperFastPython.com