Python Threading and Multithreading

In this python tutorial, we will discuss python threading and multithreading. We will also check:

  • What is a thread?
  • What is threading?
  • Python thread creating using class
  • Python threading lock
  • Threads using queue
  • Python Multi-thread creating using functions
  • Synchronization in Multithreading
  • Python thread pool
  • Multithreading vs Multiprocessing
  • Thread vs Multithread

What is a thread?

A thread is the smallest unit that is scheduled in an operating system, which can perform multitask simultaneously. When the task requires some time for execution python threads are used in this scenario.

Introduction to Python threading

  • Threading is a process of running multiple threads at the same time.
  • The threading module includes a simple way to implement a locking mechanism that is used to synchronize the threads.

In this example, I have imported a module called threading and time. Also, we will define a function Evennum as def Evennum(). We have used for loop and range() function and also sleep() is used to wait for executing the current thread for given seconds of time.

Example:

import threading
import time

def Evennum():
    for i in range(2, 10, 2):
        time.sleep(1)
        print(i)

threading.Thread(target=Evennum).start()

Below screenshot shows the evennumbers from the range 2 to10.

Python Threading
Python Threading

Python thread creating using class

Here, we can see how to create thread using class in python.

Syntax to create thread class:

Thread(group=None, target=None, name=None, args=(), kwargs={})

To create a thread using class in python there are some class methods:

  • run() – This method calls the target function that is passed to the object constructor.
  • start() – Thread activity is started by calling the start()method, when we call start() It internally invokes the run method and executes the target object.
  • join([timeout]) – This method blocks calling thread until the thread whose join() is called terminates normally or through handle exception.
  • getName() – This method returns a name for the thread.
  • setName(name) – This method is used to set the thread name, the name in the form of a string, and is used for identification.
  • isAlive() – This method returns that the thread is alive or not. The thread is alive at the time when the start() is invoked and lasts until the run() terminates.
  • setDaemon(Daemonic) – This method is used to set the daemon flag to Boolean value daemonic. this should be called before the start().
  • isDaemon() – This method returns the value of the thread’s daemon flag.
  • In this example, I have imported a module called thread from threading and defined a function as a threaded function and an argument is passed.
  • The value of __name__ attribute is set to “__main__”. When the module is run as a program. __name__ is the inbuilt variable that determines the name for a current module.
  • If the module is running directly from the command line then “__name__” is set to “__main__”.

Example:

from threading import Thread 
def threaded_function(arg): 
    for i in range(arg): 
        print("python guides")
if __name__ == "__main__": 
    thread = Thread(target = threaded_function, args = (3, )) 
    thread.start() 
    thread.join() 
    print("Thread Exiting...") 

You can see in the below screenshot that python guides printed three times as mentioned in the range().

Python thread creating using class
Python thread creating using class

Python threading lock

The threading module has a synchronization tool called lock. A lock class has two methods:

  • acquire(): This method locks the Lock and blocks the execution until it is released.
  • release(): This method is used to release the lock. This method is only called in the locked state.
  • In this example, I have imported called Lock from threading, lock = Lock() used to declare a lock, and defined function to multiply the value as def multiply_one().
  • lock.acquire() is used to lock when the state is unlocked and return immediately.
  • lock.release() is used to unlock the state this is only called when the state is locked.
  • Threads.append(Thread(target=func)) used to instantiate it with a target function.
  • threads[-1].start() used to call start, print(a)gives the final value.

Example:

from threading import Lock, Thread
lock = Lock()
a = 1

def multiply_one():
   
   global a
   lock.acquire()
   a *= 4
   lock.release()

def multiply_two():
   global a
   lock.acquire()
   a *= 6
   lock.release()

threads = []
for func in [multiply_one, multiply_two]:
   threads.append(Thread(target=func))
   threads[-1].start()

for thread in threads:
 
   thread.join()

print(a)

You can refer the below screenshot to see the multiplied value .

Python threading lock
Python threading lock

Threads using queue

  • In this example, I have imported modules called queue and threading. The function employee is used as a def employee().
  • Infinite loop(while True) is called to make threads ready to accept all the tasks.
  • Then define queue as project = q.get() .
  • task_done() tells the queue that the processing on task is completed. When the project is put in the queue task_done is called.
  • threading.Thread(target=employee, daemon=True).start() is used to start the employee thread.
  • for a time in range(5)means 5 tasks are sent to the employee.
  • q.join blocks till all the tasks are completed.

Example:

import threading, queue
q = queue.Queue()
def employee():
    while True:
        project = q.get()
        print(f'working on {project}')
        print(f'done{project}')
        q.task_done()
threading.Thread(target=employee, daemon=True).start()
for project in range(5):
    q.put(project)
print('project requests sent\n', end='')
q.join()
print('projects completed')

You can refer the below screenshot to see output of all the 5 tasks.

Threads using queue
Threads using queue

What is Multithreading in Python?

A process of executing multiple threads parallelly. Multi-threads use maximum utilization of CPU by multitasking. Web Browser and Web Server are the applications of multithreading.

Python Multithread creating using functions

  • In this example, I have imported threading and defined a function, and performed arithmetic operations.
  • The format() returns a formatted string. t1.start() to start the thread. t1.join() performs the main thread to wait until the other thread to finish.
  • The value of __name__ attribute is set to “__main__”. When the module is run as a program. __name__ is the inbuilt variable that determines the name for a current module.
  • If the module is running directly from the command line then “__name__” is set to “__main__”.

Example:

import threading 
def multiplication(num): 
    print("Multiplication: {}".format(num * num)) 
def addition(num): 
    print("Addition: {}".format(num + num)) 
def division(num):
    print("Division: {}".format(num / num))
def substraction(num):
    print("substraction: {}".format(num - num))
if __name__ == "__main__":  
    t1 = threading.Thread(target=multiplication, args=(20,)) 
    t2 = threading.Thread(target=addition, args=(5,))  
    t3 = threading.Thread(target=division, args=(100,))
    t4 = threading.Thread(target=substraction, args=(3,))
    t1.start()  
    t2.start() 
    t3.start()
    t4.start()
    t1.join() 
    t2.join()
    t3.join()
    t4.join()

You can refer the below screenshot to check the arithmetic operation.

Python thread creating using functions
Python thread creating using functions

Synchronization in Multithreading

Shared resource is protected by thread synchronization by ensuring that one thread is accessed at a time. It also protects from race conditions.

What is race condition?

The shared resource is accessed by multiple threads at a time. All threads racing to complete the task and finally, it will end up with inconsistent data. To over this race condition synchronization method is used.

  • Here, we have to create a shared resource. shared resource generates a multiplication table for any given number.
  • In this example, I have imported a module called threading and defined class Multiplication and defined a function Mul as def Mul.
  • Then I have used for the loop in the range(1,6) and then used print(num, ‘X’ ,i, ‘=’ ,num*i). Here num is the number whichever you give and ‘X’ is the multiplication symbol, i is the range given.
  • Then defined another class as MyThread and defined a constructor as def__init__(self,tableobj,num). and in the constructor, I have defined a superclass constructor.
  • To include the shared resource in this thread we need an object and used tableobj as a parameter and another parameter as num is used.
  • Again defined a run function using def run(self).
  • Thread lock is created using threadlock=Lock().
  • This lock should be created before accessing the shared resource and after accessing the shared resource, we have to release using threadlock.release().
  • To get the output self.tableobj.Mul(self.num) is used.
  • Shared resource object is created using tableobj=Multiplication().
  • Create two threads using t1=MyThread(tableobj,2)and then pass parameter tableobj and a number.
  • After that, we have to start the thread using t1.start().

Example:

from threading import *
class Multiplication:
    def Mul(self,num):
        for i in range(1,6):
            print(num,'X',i,'=',num*i)
class MyThread(Thread):
    def __init__(self,tableobj,num):
        Thread.__init__(self)
        self.tableobj=tableobj
        self.num=num
    def run(self):
        threadlock.acquire()
        self.tableobj.Mul(self.num)
        threadlock.release()
threadlock=Lock()
tableobj=Multiplication()
t1=MyThread(tableobj,2)
t2=MyThread(tableobj,3)
t1.start()
t2.start()

Here, In the below screenshot we can see the multiplication tables of number 2 and 3.

Synchronization in Multithreading
Synchronization in Multithreading

You can refer the below screenshot for improper arrangement of data.

  • In this output, we can see the improper arrangement of data. This is due to a race condition. To overcome the race condition synchronization is used.
  • Sometimes we may get proper data but many times we will be getting improper data arrangement.
  • So, it is better to use the synchronization method (lock class).
Synchronization in Multithreading
Synchronization in Multithreading

Python thread pool

  • Thread pool is a group of worker threads waiting for the job.
  • In a thread pool, a group of a fixed size of threads is created.
  • A service provider pulls the thread from the thread pool and assigns the task to the thread.
  • After finishing the task a thread is returned into the thread pool.
  • The advantage of the thread pool is thread pool reuses the threads to perform the task, after completion of a task the thread is not destroyed. It is returned back into the thread pool.
  • Threadpool is having better performance because there is no need to create a thread.
  • In this example, I have imported a module called concurrent.futures this module provides an interface for processing the tasks using pools of thread.
  • The NumPy module is also imported which is used to work with the array.
  • Time modules handle various functions related to time.
  • ThreadPoolExecutor is an executor subclass that uses thread pools to execute the call. By using ThreadPoolExecutor 2 threads have been created.
  • Then the task is given by the function in the form of an argument as a number and wait for 2 sec to execute the function and display the results.
  • When the task is completed the done() returns a True value.
  • submit() method is a subinterface of executor. The submit() accepts both runnable and callable tasks.

Example:

import concurrent.futures
import numpy as np
from time import sleep
numbers = [1,2,3,]
def number(numbers):
    sleep(2)
print(numbers)
with concurrent.futures.ThreadPoolExecutor(max_workers = 2) as executor:
    thread1 = executor.submit(number, (numbers))
print("Thread 1 executed ? :",thread1.done())

In this below screenshot, we can see that done() returns the true value after finishing the task.

Python thread pool
Python thread pool

Multithreading vs Multiprocessing

MultithreadingMultiprocessing
Multithreading allows a single process that contains many threads.Multiprocessing is a system that contains two or more processors.
It is used to create threads in a single process.It is used to increase computing power.
Job processing is done in less time.Job process is done in moderate time
The creation of the process is slow.The creation of a thread is faster.
Multithreading vs Multiprocessing

Thread vs Multithread

ThreadMultithread
A thread is the smallest unit that can perform multitask simultaneously.A process of executing multiple threads parallelly.
Threads execute within the process.Multithread has many threads executing in the shared address space.
A single thread is used to execute the task.Multiple threads are used to execute the task.
Thread vs Multithread

You may like the following Python tutorials:

In this Python tutorial, we have learned about Python threading and multithreading. Also, We covered these below topics:

  • What is Threading?
  • What is Multithreading?
  • Python thread creating using class
  • Python threading lock
  • Threads using queue
  • Python Multi-thread creating using functions
  • Synchronization in Multithreading
  • Python thread pool
  • Multithreading vs Multiprocessing
  • Thread vs Multithread