ThreadPoolExecutor When Are Workers Started

July 18, 2023 Python ThreadPoolExecutor

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:

...
# 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:

  1. Immediately after an instance of the class is created.
  2. On-demand as tasks are issued to the pool.

We may need to know this for many reasons, such as:

When are worker threads created in the ThreadPoolExecutor?

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.

...
# 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:

...
# 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.

# 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.

...
# 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.

...
# 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.

...
# 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.

# 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.

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)>

Takeaways

You now know when worker threads in the ThreadPoolExecutor are created.



If you enjoyed this tutorial, you will love my book: Python ThreadPoolExecutor Jump-Start. It covers everything you need to master the topic with hands-on examples and clear explanations.