BlogsDope image BlogsDope

Multithreading in Python

June 27, 2020 PYTHON 287

A thread is a basic unit of CPU utilization. It is an execution sequence within a process. Every process has at least one thread, i.e., the process itself, and it can have multiple threads. Each thread has its thread ID, a program counter, a register set, and a stack. It shares a code section, data section, and other OS resources with other threads, which belong to the same process.

You probably have wondered at this point, why do we need multiple threads? As we know that a process has a single thread. If it blocks for some reason, let’s say waiting for an I/O to complete, the process can make no further progress. However, if the process has multiple threads, and if one of the threads is blocked, some other thread can run, and thus, some activity can be done as part of that process. Check the figure below.

Multithreading in Python

Multithreading allows Python code to run concurrently, i.e., only one thread can run at one time. However, they make progress simultaneously. This is because of the GIL (Global Interpretation Lock) in the CPython (which is the original implementation of the python programming language). It only allows one thread to hold control of the Python interpreter.

They are two types of threads. Kernel level threads and User level threads. Kernel level threads are created and managed by the kernel. User level threads are created and managed in the user space using some library.

The threading module in Python allows us to take advantage of multithreading.

Let’s get started with a simple example.

import time
import threading


def test(x):
    print(f"Entering the thread {x}")
    time.sleep(x)
    print(f"Done..{x}")


if __name__ == "__main__":
    threads = []
    for i in range(0, 5):
        thread = threading.Thread(target=test, args=(i,))
        threads.append(thread)
        thread.start()
    for thread in threads:
        thread.join()

Output

Entering the thread 0
Done..0
Entering the thread 1
Entering the thread 2
Entering the thread 3
Entering the thread 4
Done..1
Done..2
Done..3
Done..4

In the first and second lines, we import the time and the threading module.

Next, we have the test() function that sleeps for x seconds.

In the main part of the python script, we create five threads using a for loop. Each of the thread executes the test() function and sleeps for i (the current iteration number) seconds.

The syntax to create a thread object is threading.thread(target=None, name=None, args=(),keywordargs={}). The target argument represents the function to be executed by the thread, and args is the argument tuple passed to it. name represents the name of the thread. keywordargs is a dictionary of keyword arguments passed to the target function.

The thread.start() method starts the thread. It arranges for the thread’s run() method to execute in a new thread. The run() method represents the thread’s task.

The thread.join() method blocks the calling thread and waits for the thread to finish its execution. You can also pass an optional timeout argument, which will wait for timeout seconds for the thread to terminate. If the thread does not terminate by then, the method returns.

Consider another example to see some other methods.

import time
import threading
import os


def test(x):
    thread = threading.current_thread()
    print(f"Current Thread Name: {thread.name}")
    print(f"Current thread Id:{threading.get_ident()}")
    time.sleep(3)
    print(f"Done..{x}")


if __name__ == "__main__":
    threads = []
    for i in range(0, 2):
        thread = threading.Thread(target=test, args=(i,))
        threads.append(thread)
        thread.start()
    for thread in threads:
        thread.join()

Output

Current Thread Name: Thread-78
Current thread Id:139936186214144
Current Thread Name: Thread-79
Current thread Id:139936169166592
Done..0
Done..1

In the above example, we display some information about the created thread in the test() function. The threading.current_thread() method returns the current thread object. We use the name attribute of the thread object to display the name of the thread. Note that more than one thread can have the same name.

The threading.get_ident() method returns the thread ID in which it is called. These IDs can be reused when the thread to which it is assigned exits, and another thread starts. While the threading.get_ident() returns the thread identifier of the thread in which it is called, the thread.ident attribute returns the thread identifier of the thread object. The identifier is available even after the thread has terminated.

The threading.get_native_id() method returns the native thread ID assigned by the kernel. These IDs can be reused when the thread to which it is assigned exits, and another thread starts. It may uniquely be used to identify a thread in the whole system. This method is available in Python version 3.8. The thread.native_id returns the native ID of the thread object.

The threading.main_thread() returns the object of the main thread. Let’s see.

import time
import threading
import os


def test():
    time.sleep(3)
    print(f"Main Thread: {threading.main_thread()}")


if __name__ == "__main__":
    thread = threading.Thread(target=test)
    thread.start()
    print(f"Main Thread: {threading.main_thread()}")
    thread.join()

Output

Main Thread: <_MainThread(MainThread, started 139936820684672)>
Main Thread: <_MainThread(MainThread, started 139936820684672)>

The threading.active_count() method returns the total number of threads currently alive and the threading.enumerate() returns the list of these objects. It also includes daemonic threads (A daemonic thread is a thread that does not block the main program from exiting and runs in the background), dummy threads created by the threading.current_thread() method and the main thread.

The thread.is_alive() method returns whether the thread is alive or not. Consider the following example.

import time
import threading
import os


def test(x):
    time.sleep(3)


if __name__ == "__main__":
    threads = []
    for i in range(0, 2):
        thread = threading.Thread(target=test, args=(i,))
        threads.append(thread)
        print("Check before starting the thread")
        print(thread.is_alive())
        thread.start()
        print("Check after starting the thread")
        print(thread.is_alive())
    for thread in threads:
        thread.join()

Output

Check before starting the thread
False
Check after starting the thread
True
Check before starting the thread
False
Check after starting the thread
True

A thread is alive after the run() method starts until it terminates.


Liked the post?
A computer science student having interest in web development. Well versed in Object Oriented Concepts, and its implementation in various projects. Strong grasp of various data structures and algorithms. Excellent problem solving skills.
Editor's Picks
0 COMMENT

Please login to view or add comment(s).