The ThreadPoolExecutor will create worker threads on demand, rather than creating all worker threads when the thread pool is created.
In this tutorial, you will discover when worker threads in the ThreadPoolExecutor are created.
Let’s get started.
Need to Know When Workers Are Started 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 = tpe.submit(task) # the the task result once the task is done result = future.result() |
You can learn more about the ThreadPoolExecutor in the tutorial:
When using the ThreadPoolExecutor, we may need to know when the worker thread in the pool is created and started.
There are two main options for when child workers could be created:
- Immediately after an instance of the class is created.
- On-demand as tasks are issued to the pool.
We may need to know this for many reasons, such as:
- Will there be a lag when the first task is issued if worker threads are created on demand?
- Will the pool need to be closed correctly if no tasks have been issued to the pool?
When are worker threads created in the ThreadPoolExecutor?
Run loops using all CPUs, download your FREE book to learn how.
When Workers Are Started in the ThreadPoolExecutor
Worker threads in the ThreadPoolExecutor are created as tasks are issued to the pool.
This means that when the ThreadPoolExecutor is created with a given number of workers, no worker threads are actually created.
1 2 3 |
... # create a thread pool tpe = ThreadPoolExecutor(10) |
It is not until a task is issued to the thread pool that a worker thread is created.
For example:
1 2 3 |
... # issue one task to the pool future = tpe.submit(task) |
New worker threads are created in the pool until the default or configured number of workers has been created, after which they will be reused.
Now that we know when the worker threads are created in the ThreadPoolExecutor, let’s look at a worked example.
Example of Showing When Workers Are Started in the ThreadPoolExecutor
We can explore an example that shows when worker threads are started in the ThreadPoolExecutor.
In this example we will create a thread pool and then report all threads running in the process, to confirm that worker threads are started immediately. We will then issue a task, wait a moment, then report all threads running in the process to confirm that worker threads are created on demand. Finally, we will shut down the thread pool and confirm that all worker threads are terminated along with the thread pool.
Firstly, we will define a task to execute in the thread pool. The task blocks for a moment to simulate doing work.
1 2 3 4 |
# task to execute in the thread pool def task(): # block for a moment to simulate work sleep(1) |
Next, we will create the thread pool with 10 worker threads, sleep for a moment, then report all threads running in the process.
We can use the threading.enumerate() function to enumerate all running threads in the process and report their details.
We expect no worker threads to have been created yet.
1 2 3 4 5 6 7 8 9 |
... # create a thread pool tpe = ThreadPoolExecutor(10) # wait a moment sleep(1) # report all threads print('ThreadPoolExecutor created:') for thread in threading.enumerate(): print(f' {thread}') |
Next, we will issue one task using the submit() method, wait a moment, then report all running threads.
We expect to see at least one worker thread to have been created to handle the task.
1 2 3 4 5 6 7 8 9 |
... # issue a task _ = tpe.submit(task) # wait a moment sleep(1) # report all threads print('ThreadPoolExecutor task issued:') for thread in threading.enumerate(): print(f' {thread}') |
Next, we will shut down the thread pool via the shutdown() method, wait a moment, and report all worker threads.
We expect all worker threads to have been terminated.
1 2 3 4 5 6 7 8 9 |
... # shutdown the thread pool tpe.shutdown() # wait a moment sleep(1) # report all threads print('ThreadPoolExecutor shutdown:') for thread in threading.enumerate(): print(f' {thread}') |
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 |
# SuperFastPython.com # example of showing when workers are started in the threadpoolexecutor from concurrent.futures import ThreadPoolExecutor from time import sleep import threading # task to execute in the thread pool def task(): # block for a moment to simulate work sleep(1) # create a thread pool tpe = ThreadPoolExecutor(10) # wait a moment sleep(1) # report all threads print('ThreadPoolExecutor created:') for thread in threading.enumerate(): print(f' {thread}') # issue a task _ = tpe.submit(task) # wait a moment sleep(1) # report all threads print('ThreadPoolExecutor task issued:') for thread in threading.enumerate(): print(f' {thread}') # shutdown the thread pool tpe.shutdown() # wait a moment sleep(1) # report all threads print('ThreadPoolExecutor shutdown:') for thread in threading.enumerate(): print(f' {thread}') |
Running the example first creates the thread pool and reports all running threads.
In this case, we can confirm that no worker threads are created when the ThreadPoolExecutor is created. We can see that the main thread is the only active thread.
Next, the task is issued and all active threads are reported.
We can see that one worker thread was created to handle the single task that was issued. It has the name “ThreadPoolExecutor-0_0“, signaling that it belongs to the ThreadPoolExecutor.
Next, the thread pool is shut down and all active threads are reported. We can see that the main thread is the only remaining active thread, confirming that the worker thread in the ThreadPoolExecutor was terminated.
This highlights that worker threads in the ThreadPoolExecutor are created on-demand as tasks are issued to the thread pool.
1 2 3 4 5 6 7 |
ThreadPoolExecutor created: <_MainThread(MainThread, started 4668562944)> ThreadPoolExecutor task issued: <_MainThread(MainThread, started 4668562944)> <Thread(ThreadPoolExecutor-0_0, started 123145459007488)> ThreadPoolExecutor shutdown: <_MainThread(MainThread, started 4668562944)> |
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 when worker threads in the ThreadPoolExecutor are created.
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
Do you have any questions?