Last Updated on October 29, 2022
In this tutorial, you will discover the difference between the ThreadPool and Thread and when to use each in your Python projects.
Let’s get started.
What Is The ThreadPool
The multiprocessing.pool.ThreadPool class in Python provides a pool of reusable threads for executing ad hoc tasks.
A thread pool object which controls a pool of worker threads to which jobs can be submitted.
— multiprocessing — Process-based parallelism
The ThreadPool class extends the Pool class. The Pool class provides a pool of worker processes for process-based concurrency.
Although the ThreadPool class is in the multiprocessing module it offers thread-based concurrency.
Recall that a thread is a thread of execution. Each thread belongs to a process and can share memory (state and data) with other threads in the same process. In Python, like many modern programming languages, threads are created and managed by the underlying operating system, so-called system threads or native threads.
We can create a thread pool by instantiating the ThreadPool class and specifying the number of threads via the “processes” argument; for example:
1 2 3 |
... # create a thread pool pool = ThreadPool(processes=10) |
We can issue one-off tasks to the ThreadPool using methods such as apply() or we can apply the same function to an iterable of items using methods such as map().
The map() function matches the built-in map() function and takes a function name and an iterable of items. The target function will then be called for each item in the iterable as a separate task in the thread pool. An iterable of results will be returned if the target function returns a value.
For example:
1 2 3 4 |
... # call a function on each item in a list and handle results for result in pool.map(task, items): # handle the result... |
The ThreadPool class offers many variations on the map() method for issuing tasks, you can learn more in the tutorial:
We can issue tasks asynchronously to the ThreadPool, which returns an instance of an AsyncResult immediately. One-off tasks can be used via apply_async(), whereas the map_async() method offers an asynchronous version of the map() method.
The AsyncResult object provides a handle on the asynchronous task that we can use to query the status of the task, wait for the task to complete, or get the return value from the task, once it is available.
For example:
1 2 3 4 5 |
... # issue a task to the pool and get an asyncresult immediately result = pool.apply_async(task) # get the result once the task is done value = result.get() |
Once we are finished with the ThreadPool, it can be shut down by calling the close() method in order to release all of the worker threads and their resources.
For example:
1 2 3 |
... # shutdown the thread pool pool.close() |
The life-cycle of creating and shutting down the thread pool can be simplified by using the context manager that will automatically close the ThreadPool.
For example:
1 2 3 4 5 6 7 8 |
... # create a thread pool with ThreadPool(10) as pool: # call a function on each item in a list and handle results for result in pool.map(task, items): # handle the result... # ... # shutdown automatically |
You can learn more about how to use the ThreadPool class in the tutorial:
Now that we are familiar with ThreadPool, let’s take a look at Thread.
Run loops using all CPUs, download your FREE book to learn how.
What Is Thread
The Thread class represents a thread of execution in Python.
There are two main ways to use a Thread; they are:
- Execute a target function.
- Extend the class and override run()
Execute a Target Function
The Thread class can execute a target function in another thread.
This can be achieved by creating an instance of the Thread class and specifying the target function to execute via the “target” keyword.
The thread can then be started by calling the start() function and it will execute the target function in another thread.
For example:
1 2 3 4 5 6 7 8 |
# a target function that does something def work() # do something... # create a thread to execute the work() function thread = Thread(target=work) # start the thread thread.start() |
If the target function takes arguments, they can be specified via the “args” argument that takes a tuple or the “kwargs” argument that takes a dictionary.
For example:
1 2 3 |
... # create a thread to execute the work() function thread = Thread(target=work, args=(123,)) |
The target task function is useful for executing one-off ad hoc tasks that probably don’t interact with external state other than passed-in arguments and do not return a value
You can learn more about running a function in a new thread in the tutorial:
Extend the Class
The thread class can be extended for tasks that may involve multiple functions and maintain state.
This can be achieved by extending the Thread object and overriding the run() function. The overridden run() function is then executed when the start() function of the thread is called.
For example:
1 2 3 4 5 6 7 8 9 10 11 |
# define a custom thread class CustomThread(Thread): # custom run function def run(): # do something... # create the custom thread thread = CustomThread() # start the thread thread.start() |
Overriding the Thread class offers more flexibility than calling a target function. It allows the object to have multiple functions and to have object member variables for storing state.
Extending the Thread class is suited for longer-lived tasks and perhaps services within an application.
You can learn more about extending the threading.Thread class in the tutorial:
Now that we are familiar with the ThreadPool and Thread, let’s compare and contrast each.
Comparison of ThreadPool vs. Thread
Now that we are familiar with the ThreadPool and Thread classes, let’s review their similarities and differences.
Similarities Between ThreadPool and Thread
The ThreadPool and Thread classes are very similar. Let’s review some of the most important similarities.
1. Both Use Thread
Both the ThreadPool and Thread are based on Python threads.
Python supports real system-level or native threads, as opposed to virtual or green threads. This means that Python threads are created using services provided by the underlying operating system.
The Thread class is a representation of system threads supported by Python. The ThreadPool makes use of Python Threads internally and is a high level of abstraction.
2. Both Can Run Ad Hoc Tasks
Both the ThreadPool class and the Thread can be used to execute ad hoc tasks.
The ThreadPool can execute ad hoc tasks via the apply(), map(), and various other methods.
Whereas the Thread class can execute ad hoc tasks via the “target” argument.
3. Both Are Subject to the GIL
Both the ThreadPool class and the Thread are subject to the global interpreter lock (GIL).
The GIL is a programming pattern in the reference Python interpreter (e.g. CPython, the version of Python you download from python.org). It is a lock in the sense that it uses synchronization primitives to ensure that only one thread of execution can execute instructions at a time within a Python process.
This means that although we may have multiple threads in a ThreadPool or multiple instances of Thread, only instructions from one thread can execute at a time in a Python process.
This constraint, in turn, suggests that both ThreadPool and Thread are well suited to IO-bound tasks and not CPU-bound tasks.
Differences Between ThreadPool and Thread
The ThreadPool and Thread are also quite different. Let’s review some of the most important differences.
1. Heterogeneous vs. Homogeneous Tasks
The ThreadPool is for heterogeneous tasks, whereas Thread is for homogeneous tasks.
The ThreadPool is designed to execute heterogeneous tasks, that is tasks that do not resemble each other. For example, each task submitted to the thread pool may be a different target function.
The Thread class is designed to execute homogeneous tasks. For example, if the Thread class is extended, then it only supports a single task type defined by the custom class.
2. Reuse vs. Single-Use
The ThreadPool supports reuse, whereas the Thread class is for single use.
The ThreadPool class is designed to submit many ad hoc tasks at ad hoc times throughout the life of a program. The threads in the pool remain active and ready to execute work until the pool is shut down.
The Thread class is designed for single use. This is the case regardless of using the target argument or extending the class. Once the Thread has executed the task, the object cannot be reused and a new instance must be created.
3. Multiple Tasks vs. Single Task
The ThreadPool supports multiple tasks, whereas the Threads class supports a single task.
The ThreadPool is designed to submit and execute multiple tasks. For example, the map() method is explicitly designed to perform multiple function calls concurrently.
Additionally, the ThreadPool class supports asynchronous tasks, providing an AsyncResult object as a handle on tasks that are executed in a fire and forget manner.
The Thread class is designed for executing a single task, either via the target argument or by extending the class. There are no built-in tools for managing multiple concurrent tasks; instead, such tools would have to be developed on a case-by-case basis.
Summary of Differences
It may help to summarize the differences between ThreadPool and Thread.
ThreadPool
- Heterogeneous tasks, not homogeneous tasks.
- Reuse threads, not single use.
- Manage multiple tasks, not single tasks.
- Support for task results, not fire-and-forget.
- Check the status of tasks, not opaque.
Thread
- Homogeneous tasks, not heterogeneous tasks.
- Single-use threads, not multi-use threads.
- Manage a single task, not manage multiple tasks.
- No support for task results.
- No support for checking status.
The figure below provides a helpful side-by-side comparison of the key differences between ThreadPool and Thread.
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
How to Choose ThreadPool or Thread
When should you use ThreadPool and when should you use Thread? Let’s review some useful suggestions.
When to Use ThreadPool
Use the ThreadPool class when you need to execute many short- to modest-length tasks throughout the duration of your application.
Use the ThreadPool class when you need to execute tasks that may or may not take arguments and may or may not return a result once the tasks are complete.
Use the ThreadPool class when you need to execute different types of ad hoc tasks, such as calling different target task functions.
Use the ThreadPool class when the types of tasks and timing of when you need to execute tasks vary at runtime.
Use the ThreadPool class when you need to be able to queue up a large number of tasks.
Use the ThreadPool class when you need to be able to check on the status of tasks during their execution.
Use the ThreadPool class when you need to take action based on the results of tasks, such as the first task to complete, the first task to raise an exception, or results as they become available.
Don’t Use ThreadPool When…
Don’t use the ThreadPool for complex tasks that may be spread across multiple function calls. Instead, you may be better suited to extending the Thread class and encapsulating all the functions for the task.
Don’t use the ThreadPool for tasks that require the management of a lot of state. Instead, you may be better suited to extending the Thread class and managing state as instance variables.
Don’t use the ThreadPool for single one-off tasks. Instead, you may be better suited to using the Thread class with the target argument.
Don’t use the ThreadPool for long-running tasks. You might be better suited to extending the Thread class and defining the long-duration task.
When to Use Thread
Use the Thread class when you have a single one-off task to execute via the “target” argument.
Use the Thread class for many similar tasks with different arguments that do not return a result, such as via the “target” argument or by multiple instances of a customized Thread class.
Use the Thread class when you have a lot of complex behavior spread across multiple functions and/or when you have a lot of state to be managed. In these cases, you can extend the Thread class and define your instance variables and task functions.
Use the Thread class for long-running tasks by extending the Thread class and treating the object as a service within your application.
Don’t Use Thread When…
Don’t use the Thread class for many different task types, e.g. different target functions. You are better off using the ThreadPool.
Don’t use the Thread class when you require a result from tasks; you could achieve this by extending the Thread class, although it’s easier with the ThreadPool.
Don’t use the Thread class when you need to execute and manage multiple tasks concurrently. This could be achieved with Thread but would require developing the tools and infrastructure.
Don’t use the Thread class when you are required to check on the status of tasks while they are executing; this can be achieved with Future objects returned when submitting tasks to the ThreadPool.
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
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
Takeaways
You now know the difference between ThreadPool and Thread and when to use each.
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Photo by Neil Mark Thomas on Unsplash
Do you have any questions?