What is Multithreading in Python? Examples, Benefits, Uses
Table of Contents
- Introduction
- What is a Thread?
- What is Multithreading in Python?
- Example of Multithreading in Python
- Benefits of Multithreading in Python
- When to use Multithreading in Python?
- How to Achieve Multithreading in Python?
- Working With Multiple Threads Using ThreadPoolExecutor
- Python Multithreading FAQs
Introduction
It is believed that multitasking is not meant for all humans. Only a few are able to master it. However, if there is a machine to help us multitask, we all will be able to enhance our performance and save ample time.
Today, we are going to discuss a popular and useful multitask approach- Python Multithreading.
Multithreading is an ability of an operating system or program to enable multiple tasks at a time without requiring multiple copies of the program. It can handle more than one request from the same user, and each request for system service is tracked as a thread, serving as a separate identity.
As a program works on the initial thread request and is interrupted by other demands, the status of the former request is tracked until the work is done. A user can also be a program.
What is a Thread?
In computing, a process is an instance of an executed computer program. It consists of three basic components:
-
An executable program.
-
The associated data a program needs (variables, buffers, workspace, etc.)
-
The execution context of a program (State of the process)
A thread means a lightweight process. It is an entity in a process scheduled for execution and is also known as an instance of a process. Thread is the smallest processing unit that can be performed in an operating system. We can say it is an independent flow of execution.
In simple terms, a thread is a sequence of instructions in a program that is executable independently of other code. Based on the situation, a thread can be put to sleep or pre-empted, which refers to temporarily stopping a task to continue it later. So, a thread can be interrupted or stopped temporarily by the processor.
What is Multithreading in Python?
Multithread in Python is a great and efficient way to improve performance and save time. Multithreading is a popular Python technique that enables various strings to run simultaneously. It allows one to perform multiple tasks together and share data space with the primary threads of a process. This is easier than the individual processes to communicate and share information with other threads.
Simply put, Python multithreading is the ability of a processor to execute more than one thread concurrently. It facilitates data space and resource sharing of multiple threads with the main thread. The main aim of multithreading is to complete multiple tasks at the same time, which enhances application performance and rendering.
There can be multiple threads within one process if:
-
Each thread has its register set and local variables.
-
All threads share global variables and the program code.
Example of Multithreading in Python
Here's an example that shows multithreading in Python:
import threading
def print_numbers():
for i in range(1, 6):
print("Thread 1:", i)
def print_letters():
for letter in ['A', 'B', 'C', 'D', 'E']:
print("Thread 2:", letter)
# Create two threads
thread1 = threading.Thread(target=print_numbers)
thread2 = threading.Thread(target=print_letters)
# Start the threads
thread1.start()
thread2.start()
# Wait for the threads to complete
thread1.join()
thread2.join()
print("Execution completed.")
In this example, two threads (thread1 and thread2) are created, each targeting a specific function (print_numbers and print_letters). The start() method is called on each thread to begin their execution. The join() method is used to wait for the threads to complete before proceeding. Finally, a message is printed indicating the completion of the execution.
When you run this code, you'll observe that the two threads run concurrently, printing numbers and letters interleaved, demonstrating the concept of multithreading.
Benefits of Multithreading in Python
Multithreading in Python offers several benefits that make it a popular approach among programmers. Some of the advantages of multithreading are as follows:
-
It ensures efficient utilisation of resources as multiple threads share the data memory and space.
-
It is also cost-effective as it shares state with sub-threads.
-
The system doesn’t need a lot of memory space to store multiple threads.
-
Applications with multiple threads take less response time. The reduction in response time improves performance.
-
It enables powerful usage of PC framework assets.
-
Multithreading enables the simultaneous occurrence of multiple tasks.
-
As multiple threads run together, it reduces processing time.
-
Improves the viability of multiprocessor engineering because of closeness.
When to use Multithreading in Python?
Multithreading is a valuable technique to work efficiently on the working of an application. It allows programmers to run multiple tasks and sub-tasks of an application concurrently.
Moreover, it enables threads to communicate effectively with the processor and share resources, such as memory, files, and data. Adding to it, multithreading in Python makes it easier to run a program, even when a part of a program is blocked or too long.
If you want to divide your tasks and applications into multiple sub-tasks and execute them simultaneously, then Python multithreading is certainly a great choice. It improves all the key aspects, such as speed and time consumption, performance, rendering, and more drastically.
Please note that multithreading must be used only when there is no inter-dependency between threads.
How to Achieve Multithreading in Python?
Now, it’s time to understand how to achieve multi-threading in Python. There is a standard library in Python known as threading used to perform multithreading.
In Python, there are two main multithreading modules to handle threads:
-
The Thread module
-
The Threading module
Thread Module in Python
The thread module was introduced in Python 3. It was designated as obsolete and can be accessed using _thread that supports backward compatibility.
Syntax:
thread.start_new_thread ( function_name, args[, kwargs] )
To implement a thread module in Python, you must import a thread module and define a function that performs tasks by setting the target with a variable.
Threading Module in Python
The threading module is used to deploy an application in Python. It is a high-level implementation, and to use multithreading, you must import a threading module into your Python program.
You must follow the given steps for threading module implementation in Python multithreading.
1. Import the threading module
Import the threading module to create a new thread by using the following syntax:
Syntax:
import threading
A threading module comprises a thread class installed to create a Python thread.
2. Declaration of the thread parameters
It includes arguments, functions, and kwargs as the parameter in the Thread() class. The target defines the function name executed by the thread, while the argos defines the arguments passed to the target function name.
3. Start a new thread
Call the thread class object to start a new thread. Call the start() method once for each thread object else it will throw an exception error.
Syntax:
t1.start()
t2.start()
4. Join method
The join() method is used in the thread class to stop the execution of the main thread and wait until the execution of the thread object is complete. Once it’s done, the execution of the main thread starts.
5. Synchronizing Threads in Python
The thread synchronisation mechanism ensures that no two threads run the same part of a program concurrently to access shared resources. To avoid such a critical condition where two threads can’t access resources at the same time, we use a race condition.
Working With Multiple Threads Using ThreadPoolExecutor
Using the `ThreadPoolExecutor` from the `concurrent.futures` module is another way to work with multiple threads in Python. The `ThreadPoolExecutor` provides a high-level interface for managing a pool of threads and executing tasks concurrently.
Here's how you can use it:
1. Import the necessary modules:
Start by importing the required modules.
import concurrent.futures
2. Define the task function:
Create a function that represents the task you want to execute concurrently. This function should take the necessary input arguments and return the result.
def task_function(arg):
# Code for the task
# Return the result
3. Create a `ThreadPoolExecutor` object:
Instantiate a `ThreadPoolExecutor` object, specifying the desired number of worker threads to be created in the pool.
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
# Code goes here
4. Submit tasks for execution:
Use the `submit()` method of the `ThreadPoolExecutor` to submit tasks for execution. Pass the task function along with its arguments as arguments to the `submit()` method. This method returns a `Future` object representing the asynchronous execution of the task.
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
future1 = executor.submit(task_function, arg1)
future2 = executor.submit(task_function, arg2)
# Submit more tasks if needed
5. Handle the results (optional):
If you want to retrieve the results of the tasks, you can use the `result()` method of the `Future` object. This method blocks until the task is complete and returns the result.
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
future1 = executor.submit(task_function, arg1)
future2 = executor.submit(task_function, arg2)
result1 = future1.result()
result2 = future2.result()
6. Process the results:
After retrieving the results, you can process them as needed.
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
future1 = executor.submit(task_function, arg1)
future2 = executor.submit(task_function, arg2)
result1 = future1.result()
result2 = future2.result()
# Process the results
7. Handle exceptions:
When working with `ThreadPoolExecutor`, it's important to handle exceptions that may occur during task execution. You can use `try-except` blocks or the `add_done_callback()` method of the `Future` object to handle exceptions.
def handle_exception(future):
try:
result = future.result()
# Process the result
except Exception as e:
# Handle the exception
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
future1 = executor.submit(task_function, arg1)
future1.add_done_callback(handle_exception)
Python Multithreading FAQs
Here are some frequently asked questions (FAQs) related to multithreading in Python:
1. How do I create and start a thread in Python?
In Python, you can create and start a thread by following these steps:
1. Define a function representing the task to be executed in the thread.
2. Create a `Thread` object, passing the task function as the target.
3. Call the `start()` method on the thread object to begin its execution.
2. How can I pass arguments to a thread function?
You can pass arguments to a thread function by either using the args parameter when creating the Thread object or by using keyword arguments with the kwargs parameter.
For example:
import threading
def my_function(arg1, arg2):
# Code goes here
thread = threading.Thread(target=my_function, args=(value1, value2))
3. Can multiple threads access shared data simultaneously?
Yes, multiple threads can access shared data simultaneously. However, caution must be exercised to avoid race conditions and data inconsistencies. Synchronization mechanisms like locks, semaphores, or thread-safe data structures can be used to ensure proper synchronization and avoid data corruption.
4. What is the Global Interpreter Lock (GIL) in Python?
The Global Interpreter Lock (GIL) is a mechanism in CPython (the default Python interpreter) that allows only one thread to execute Python bytecode at a time. This means that although Python supports multithreading, only one thread can execute Python code concurrently. The GIL helps to simplify memory management and ensures thread safety in CPython, but it can limit the performance benefits of multithreading in CPU-bound tasks.
5. How can I handle exceptions in multithreaded programs?
Exception handling in multithreaded programs requires careful consideration. Each thread should have its own exception handling mechanism. You can use `try-except` blocks within the thread's function to catch and handle exceptions. Additionally, you can use the `add_done_callback()` method or `result()` method of the `Future` object (from `concurrent.futures`) to handle exceptions raised during asynchronous task execution.