Last Updated on October 29, 2022
The ThreadPool will be closed automatically by the Python garbage collector if required.
It will not prevent the program from exiting and the worker threads will not keep running if the main thread exits without closing the pool.
In this tutorial, we will explore what happens if you forget or are unable to close the ThreadPool in your program.
Let’s get started.
Will The ThreadPool Stop The Program From Exiting?
The ThreadPool provides a pool of reusable workers for executing ad hoc tasks with thread-based concurrency.
An instance of the ThreadPool class can be created, specifying the number of workers to create, otherwise, a default number of workers will be created to match the number of logical CPUs in the system.
Once created, ad hoc tasks can be issued to the pool for execution via the apply() method. Multiple tasks may be executed in the book by calling the same function with different arguments via the map() method.
Tasks may be executed asynchronously in the pool via apply_async() and map_async(), allowing the caller to carry on with other tasks.
One concern with the ThreadPool is whether it will prevent the program from exiting if it is not explicitly closed.
For example, we may not close the ThreadPool correctly for many reasons, such as:
- We forget to call the close() or terminate() function.
- An error is raised that prevents a normal exit.
Sometimes in Python concurrency, a running thread or running child process can prevent the main process, and therefore the program from exiting normally.
Additionally, we may be concerned that if the pool is not closed properly, the worker threads will keep running if the main thread of the main process exits.
Will a running ThreadPool prevent the program from exiting normally (or will the program hang)?
Run loops using all CPUs, download your FREE book to learn how.
ThreadPool Do Not Stop The Program From Exiting
A running ThreadPool does not stop the program from exiting.
This means that we can forget to close a ThreadPool and the main thread will exit normally.
For example:
1 2 3 4 5 |
# protect the entry point if __name__ == '__main__': # create a pool pool = ThreadPool(4) # ... |
It also means that if an exception or error is raised and we are unable to close the pool normally, it will not prevent the program from exiting.
For example:
1 2 3 4 5 6 7 8 9 |
# protect the entry point if __name__ == '__main__': # create a pool pool = ThreadPool(4) # ... # something bad happens raise Exception('Something bad happened') # close the pool pool.close() |
In both cases, the pool will be closed automatically by the garbage collector in the Python interpreter (parent process) and the worker threads will be closed and released.
Although this occurs, it should not be relied upon.
Note that it is not correct to rely on the garbage collector to destroy the pool as CPython does not assure that the finalizer of the pool will be called
— multiprocessing — Process-based parallelism
Additionally, the worker threads are created as daemon threads.
This means that they are intended to run in the background and do not prevent the program from exiting.
You can learn more about daemon threads in the tutorial:
Now that we know that the ThreadPool will not stop the program from exiting and that the python garbage collector will close the pool for us if needed, let’s look at a worked example.
Example of Not Closing a ThreadPool
We can explore the effect of not closing the ThreadPool.
In this example, we will create a ThreadPool with a fixed number of workers. We will then confirm that all worker threads are running and then attempt to exit the program.
Firstly, we can create a ThreadPool with four workers.
Don’t worry if you have more or fewer CPU cores, as we will not be running any tasks.
1 2 3 |
... # create a pool pool = ThreadPool(4) |
Next, we can wait a moment for the pool to initialize completely. This is probably not required, but will likely avoid any possible race conditions in checking if the workers are running.
1 2 3 |
... # wait a moment sleep(0.5) |
We can then get a list of all active threads for the current process. This will include all workers in the ThreadPool.
This can be achieved via the threading.enumerate() function.
1 2 3 4 |
... # report all running threads for thread in threading.enumerate(): print(thread) |
We will then attempt to exit the program normally, with the ThreadPool still running.
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 |
# SuperFastPython.com # does the thread pool stop the program from exiting from time import sleep from multiprocessing.pool import ThreadPool import threading # protect the entry point if __name__ == '__main__': # create a pool pool = ThreadPool(4) # wait a moment sleep(0.5) # report all active threads for thread in threading.enumerate(): print(thread) # attempt to exit the main program without closing the pool # ... |
Running the example first creates a ThreadPool with four worker threads.
The main thread then blocks a moment to wait for the pool to initialize completely.
Next, a list of all running threads is retrieved and their details are reported.
We can see the four worker threads and three helper threads in the running ThreadPool.
We can notice that the worker threads and helper threads in the ThreadPool are daemon threads, meaning that they are intended to run in the background.
The main thread then ends normally, and the program exits.
The Python interpreter garbage collector in the process terminates the ThreadPool automatically.
1 2 3 4 5 6 7 8 |
<_MainThread(MainThread, started 4623429120)> <DummyProcess(Thread-1, started daemon 123145483235328)> <DummyProcess(Thread-2, started daemon 123145500024832)> <DummyProcess(Thread-3, started daemon 123145516814336)> <DummyProcess(Thread-4, started daemon 123145533603840)> <Thread(Thread-5, started daemon 123145550393344)> <Thread(Thread-6, started daemon 123145567182848)> <Thread(Thread-7, started daemon 123145583972352)> |
Free Python ThreadPool Course
Download your FREE ThreadPool PDF cheat sheet and get BONUS access to my free 7-day crash course on the ThreadPool API.
Discover how to use the ThreadPool including how to configure the number of worker threads and how to execute tasks asynchronously
Further Reading
This section provides additional resources that you may find helpful.
Books
- Python ThreadPool Jump-Start, Jason Brownlee (my book!)
- Threading API Interview Questions
- ThreadPool PDF Cheat Sheet
I also recommend specific chapters from the following books:
- Python Cookbook, David Beazley and Brian Jones, 2013.
- See: Chapter 12: Concurrency
- 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 ThreadPool: The Complete Guide
- Python Multiprocessing Pool: The Complete Guide
- Python ThreadPoolExecutor: The Complete Guide
- Python Threading: The Complete Guide
APIs
References
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
Takeaways
You now know what happens if you forget or are unable to close the ThreadPool.
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Photo by Jonatan Pie on Unsplash
Do you have any questions?