Python Multithreading: Benefits, Use Cases, and Comparison

Pieces 🌟 - Nov 7 '23 - - Dev Community

Multithreading is an execution method that runs multiple tasks at once, the task is handled by threads. A thread is a unit of execution, sharing resources and memory space during execution.

Python is a programming language for web development, mobile development, data scraping or visualization, AI/ML projects, and more. Python is multithreaded, but not simultaneously.

In this article, you will learn how to use Python multithreading in your projects, understand its benefits and use cases, and see a comparison between multithreading vs multiprocessing.

Let's dive into the world of Python execution techniques!

Benefits of Multithreading

Now that you understand multithreading well, let's discuss its benefits.

Here are a few benefits of multithreading Python projects:

  • Improved Performance: Multithreading improves the performance of applications by running the task simultaneously.
  • Better Responsiveness: Multithreading improves the responsiveness of your backend applications by running threads after a task is blocked. It helps in handling multiple user requests in a real-time application.
  • Scalability: Multithreading improves the scalability of an application by letting developers add more processors. It is useful when handling a large, growing number of users on a server.
  • Latency: Multithreading reduces the time of user requests in applications that need immediate attention. The threads work in sync to process the requests and responses.
  • Real-time processing: Multithreading ensures tasks or requests are executed with little delay and maintains a smooth performance during real-time data processing.

The Python Threading Module

The Python threading module lets you create and manage threads in Python programs. It executes tasks or requests concurrently on your servers or applications.

To use this module, import the threading module.

import threading
Enter fullscreen mode Exit fullscreen mode

You may be confused about why the start function is in use. The threading package contains a start function that calls a run function; therefore, the run function defined in the code will overwrite the threading package's run function.

Here is a basic example of creating and starting a thread in Python:

# Create a Thread

class Test:
    def run():
 for j in range(6):
 print("Thread 1")

class Test2:
    def run():
 for j in range(6):
 print("Thread 2")


t1 = Test
t2 = Test2

# Start The Thread 
t1.start()
t2.start()
Enter fullscreen mode Exit fullscreen mode

Save this code

Your output would look like this:

Thread 1
Thread 1
Thread 1
Thread 1
Thread 1
Thread 2
Thread 2
Thread 2
Thread 2
Thread 2
Enter fullscreen mode Exit fullscreen mode

Let's go over the code.

If you notice, despite not directly invoking the run method, the code executed as intended. Why?

The threading module includes a run method within its start function. The start function is responsible for starting the threads. The code above's run function overrides the threading package's run function.

If you change the run function defined above to something else, you will encounter an error because threading does not recognize the function name.

Secondly, If you notice, the program above creates two threads that run concurrently, but when run, it does not work as it should. Yes, it created and started the threads, but it does not function properly. It is because you are attempting to execute each thread within 6 seconds (6 times). There is no code to halt Thread 1 for a few seconds to allow Thread 2 to complete. Essentially, you have instructed your application to run Thread 1 first, followed by Thread 2. That's not how threading works.

You must import time and use the sleep function to make the threads work simultaneously. Sleep makes the thread dormant for a certain number of seconds(s) and allows the other thread to execute. This way, you can get your desired output.

from threading import Thread
from time import sleep

class Test(Thread):
    def run(self):
 for j in range(5):
 print("Thread 1")
            sleep(0.5)

class Test2(Thread):
    def run(self):
 for j in range(5):
 print("Thread 2")
            sleep(0.5)

t1 = Test()
t2 = Test2()

t1.start()
t2.start()

t1.join()
t2.join()

print("Both threads have finished")
Enter fullscreen mode Exit fullscreen mode

Save this code

Your Output:

Thread 1
Thread 2
Thread 1
Thread 2
Thread 1
Thread 2
Thread 2
Thread 1
Thread 1
Thread 2
Thread 1
Thread 2
Both threads have finished
Enter fullscreen mode Exit fullscreen mode

Before you get too confused with the threading's join function, remember that it waits for the threads to be entirely terminated or executed before allowing the main program to perform its work.

The main program is print("Both threads have finished"); thus, once threads 1 and 2 have completed their execution, the output will say, "Both threads have finished."

Multithreading vs. Multiprocessing

Multiprocessing is a computing technique that performs multiple processes simultaneously and at the same speed within a multi-core CPU. It means that all operations run at once and independently. For instance, if you are running three processes on your Python program and, for some reason, process 2 is slow, processes 1 and 3 would still be executed irrespective of process 2 speed.

Multiprocessing differs from multithreading, where each thread depends on the other during execution. In a Python multithreading environment, process 2 would be executed before process 3, no matter how slow process 2 is. It follows the order, "Process 1, Process 2, then Process 3".

Here is a code example:

import multiprocessing
import time

# Function for a fast process
def fast_process():
 print("Fast process is starting.")
    time.sleep(2)  # Simulate some work
 print("Fast process is done.")

# Function for a slow process
def slow_process():
 print("Slow process is starting.")
    time.sleep(5)  # Simulate slower work
 print("Slow process is done.")

# Function for another fast process
def another_fast_process():
 print("Another fast process is starting.")
    time.sleep(2)  # Simulate some work
 print("Another fast process is done.")

if __name__ == "__main__":
 # Create three parallel processes
    process1 = multiprocessing.Process(target=fast_process)
    process2 = multiprocessing.Process(target=slow_process)
    process3 = multiprocessing.Process(target=another_fast_process)

 # Start the processes
    process1.start()
    process2.start()
    process3.start()

 # Wait for all processes to finish
    process1.join()
    process2.join()
    process3.join()

 print("All processes have finished.")
Enter fullscreen mode Exit fullscreen mode

Save this code

Your output would look like this:

Fast process is starting.
Slow process is starting.
Another fast process is starting.
Fast process is done.
Another fast process is done.
Slow process is done.
All processes have finished.
Enter fullscreen mode Exit fullscreen mode

Difference Between Multithreading and Multiprocessing

The differences between multithreading and multiprocessing.

Practical Use Cases

Python multithreading is a powerful technique used to run concurrently within a single process. Here are some practical real-time multithreading use cases:

  1. User Interface Responsiveness: Multithreading assists in keeping the responsiveness of a Graphic User Interface(GUI) while running a background task. As a user, you can interact with a text editor by saving a document or creating a document, and the user interface is up.
  2. Web Servers: Multithreading helps handle user requests on web servers. When a server receives a request, a separate thread handles the request; this lets the server tend to multiple clients simultaneously.
  3. Encryption or Decryption: Multithreading handles the process of chunks of data to improve the speed of encryption or decryption on a server or when processing data.
  4. Database Queries: Multithreading helps process multiple server database transactions by processing each transaction on a separate thread. It improves database performance.
  5. Audio and Video Processing: Multithreading helps software or web apps that render audio and video process the data by rendering the data in parallel and saving time.
  6. Automation: Multithreading helps robotics handle the multiple sensor inputs in the required time.
  7. Game Development: Multithreading helps modern games process simulations, rendering, or AI on aspects of the game. It improves the user experience and renders high graphics.

There are a few examples of practical use cases of multithreading. Any application that handles data concurrently or wants to improve data requests on the server would benefit from using the multithreading techniques.

Conclusion

In summary, Python multithreading is a useful technique that runs tasks in a single process. This technique improves servers, software, or applications. This approach enhances user interfaces to improve user experience and data processing of mobile games, web servers, robotic projects, and more.

It is wise to approach multithreading with care as it may bring about thread synchronization or resource sharing.

Now that you have knowledge about how to use multithreading in Python, you can adopt it in other upcoming projects. It is a great, adaptable tool whose continued use shows its relevance.

For additional Python resources, visit our Python code snippet collection!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .