In most computer science courses, multithreading and concurrency in Python are taught in the backend lectures or even to a lesser extent in the Operating Systems. This results in most Computer Science graduates being at a loss when trying to write concurrent programs. However, with the advent of Web 2.0, concurrency and multithreading have gained a lot of momentum and concurrency is now more important than ever. This blog will look at multithreading and concurrency in Python. For understanding multithreading first, we need to understand the thread.

What is a Thread?

In computer programming, threads are lightweight processes that ensure that each processor executes its tasks separately on the system. Think of a thread as a flow of execution and each thread can carry out its own separate order of instructions if we use the process of multi-threading we can have our program run different parts of its program at different times they all run concurrently but not truly in parallel. Each thread takes a turn running to achieve concurrency this is due to a feature called GIL (global interpreter lock) which means only one thread can be running at one time but they can all take a turn when one thread is idle so they run concurrently but not truly in parallel. Programmers like to use Python 3 because of its multi-threading capabilities – this allows multiple processors to run simultaneously and perform tasks on a program’s behalf.

Program and task can be divided into two different categories they can be 

CPU bound: It is a program or task that spends most of its time waiting for internal events such as a task that is CPU intensive it is better to use multi-processing for the tasks that are CPU bound.

IO-bound: It is a program of tasks that spends most of its time waiting for external events such as waiting for user input for if you’re doing activities like web scraping for this type of task it’s better to use multi-threading because we can have multiple threads running concurrently but not truly in parallel.

How to use MultiThreading in Python?

For using multithreading in Python there are two main modules used in handling threads.

The thread module:

The thread module can be effective in low-level threading, there exists one module that does everything the thread module does and more. That module is the newer threading module.

The Threading Module:

The Threading module is a high-level implementation of multiple threads used in Python or concurrency in Python. Its primary goal is to allow developers to create, configure, and use multiple threads within the same program on a single computer running the Python interpreter.

List of some of the useful functions defined in the threading module:

  • activeCount() – Returns the number of active thread objects that are being used.
  • currentThread() – Returns the current object of the Thread class.
  • enumerate() Returns Lists of all the active Thread objects.

List of some of the useful Thread Class methods defined in the threading module:

  • start() –  The start method starts a new thread by calling the run() method.
  • run() –  The run method is used to indicate the activity of a thread and can be overridden by a class that extends the Thread class.
  • join() –  A join method is used to block the execution of another code until the thread is closed. 

Here is a simple example for the threading module. This will count the number of threads that are currently running in the background.

#importing the threading and time module
import threading
import time

print(threading.active_count())
print(threading. enumerate())

Output:

Whenever we run the program and we have one thread that is running that is in charge of executing our program, we can see the active count of the thread that is running in the background. In the second line, we can see a list of all of the threads that are running and the main thread is in charge of running our program here by using the concept of concurrency in Python we can have more than one thread running concurrently but not truly in parallel.

Achieving MultiThreading using Python threading module:

First, let’s create a simple program that we can use for multithreading.

#importing the threading and time module
import threading
import time

#creating task one
def Task_one():
    time.sleep(3)
    print("Task one completed")

#creating task two
def Task_two():
    time.sleep(4)
    print("Task two completed")

#creating task three
def Task_three():
    time.sleep(5)
    print("Task three completed")

t = time.time() 
#calling all the function in main thread
Task_one()
Task_two()
Task_three()

print(" Active count of the threads :",threading.active_count())
print(" list of running threads :",threading.enumerate())
print(" Time taken to complete :", time.time() - t) 

Output:

This program takes about 12 seconds to run because all of the programs are running on a single thread. Because of this, their tasks are executed sequentially and not concurrently so that the completion of one task will necessitate the completion of another before another can start.

Adding MultiThreading functions in the program using the Python threading module:

#importing the threading and time module
import threading
import time

#creating task one
def Task_one():
    time.sleep(3)
    print("Task one complete")

#creating task two
def Task_two():
    time.sleep(4)
    print("Task two complete")

#creating task three
def Task_three():
    time.sleep(5)
    print("Task three complete")

#creating new thread for task one
x = threading.Thread(target=Task_one, args=())
x.start()

#creating new thread for task two
y = threading.Thread(target=Task_two, args=())
y.start()

#creating new thread for task three
z = threading.Thread(target=Task_three, args=())
z.start()

# getting total time to run the functions 
t = time.time()

#print the active count using active count function available in the threading module
print(" Active count of the threads :",threading.active_count())
#print the list of all of the threads that are running using the enumerate function
print(" list of running threads:",threading.enumerate()) print(" Time taken to complete:", time.time() - t) 

Output:

Now there are four threads running on this task and it is taking less than five seconds to run. Since we have a thread dedicated to each of these tasks, we can run all four of them concurrently instead of just one after the previous one finishes. And you may have noticed that the active count and enumerate function is called before our three functions finished their respective tasks, that’s because the main thread is not going to wait around for these functions to complete it has its own set of instructions to do so it no longer in charge of these functions and it’s also the reason the total time taking by threads is very low (0.0009970664978027344).

Synchronizing Threads:

In threads synchronizing we can have a calling thread, in this case, our main thread waits around for another thread to finish before it can move on with its own instruction.

# here we synchronized thread x, y, and z using the join function  
x.join()
y.join()
z.join()

After adding the join function in the code our output should look like this 

Output:

In this output, you can see how our functions completed first then and the total active count is only 1 because the threads are synchronized and it’s showing the time it takes to complete the task is approx five-second.

Conclusion:

MultiThreading is nothing but the ability of a program to execute multiple tasks simultaneously. By using a threading module, you can create threads in Python and access shared variables from multiple threads in the same process. I hope this tutorial will give you a basic understanding of how to use the Multithreading module in Python. If you guys have any questions/comments regarding this process. Feel free to comment Happy coding!

Here are some useful tutorials that you can read: