What is python thread?
A thread is the smallest unit of execution in a process or application. In simple words, a thread is the smallest unit of a process that can execute independently.
A process or an application can consist of a single thread or it may have multiple threads running at the same time.
Example, suppose there is a music player which plays a song, shows a dynamic image in the background, displays trending songs etc.
Each operation is a thread which performs its task. Thus, there will be a thread which plays the song, a thread which displays background image, a separate thread which fetches and displays songs etc.
When an application executes multiple threads, it is said to be multithreaded. Also, all threads share the same memory space and hardware that is allocated to the process(or application).

Multithreading vs Multiprocessing python
There is a significant difference between these two terms.
Multithreading means more than one threads in a process while multiprocessing means more than one processes(or applications).
A common example of multiprocessing is when you are typing something on a notepad while listening to music and downloading a movie. Here, notepad is one process, music player is second and downloader is the third.

Thus, multithreading takes place in the same process while multiprocessing means altogether different processes.

Multithreading in python
Python supports creating multithreaded applications with the ability to create multiple threads to perform concurrent tasks using its threading module.
This module contains various functions and classes that provide functionality to create multiple threads, retrieve information about them and also control them using sleep() and join() functions.
This post will cover all such functionality.

How to create a thread
There are 3 ways in which a new thread can be created in python. All of these methods require python’s threading module to be imported.
Method 1 : Using Thread class
Create an object of Thread class supplying it the task to be executed and some optional arguments.
Task to be executed is written as a separate function and this function is supplied to the thread by passing the name of this function in constructor of Thread class.
Note that a thread is started by calling start() method.
Below example will make it more clear.

from threading import * 

# task to be executed by thread 
def printMessage(): 
  print("A separate thread") 
  for i in range(5): 
    # create a new thread 
    t = Thread(target = printMessage) 
    # start the thread 
    t.start()

Above program creates 5 threads in a loop and starts them. Each thread is supplied the task to be executed in the form of a function name assigned to target attribute in its constructor.

Following output is generated upon executing this program.

A separate thread
A separate thread
A separate thread
A separate thread
A separate thread

For all thread related functionality, you need to import python’s threading module.

Method 2: Using subclass of Thread
Instead of using Thread class directly to spawn a thread, we can create a child class of Thread and use it to create a thread.
In this method, we create a new class which extends Thread class and overrides its run() method. This method will contain the task or work performed by newly created threads.

For creating a new thread, create an object of this class and call start() method. When start() is called, it automatically calls run() method defined in our class in a separate thread.

Below code will help in understanding the concept.

from threading import * 

# create a child class of Thread class 
MyThread(Thread): 
  # override run method of Thread class 
  def run(self): 
    print("Thread created by extending Thread class") 
    
for i in range(5): 
  # create a new thread 
  t = MyThread() 
  # start the thread 
  t.start()

Above program when executed produces the following output

Thread created by extending Thread class
Thread created by extending Thread class
Thread created by extending Thread class
Thread created by extending Thread class
Thread created by extending Thread class

Note that we did not call run() method anywhere, it is automatically executed when we call start() method.

Method 3: Using a separate class
This method is very much similar to the first, in that the task to be performed by the thread is written in a separate function.
This function is created inside a python class and is accessed using an object of this class.

Have a look a the below code.

from threading import * 

# create a class 
class Worker: 
  # task performed by thread 
  def printMessage(self): 
    print("Task defined in separate class") 

# create object of class 
obj = Worker() 
for i in range(5): 
  # create a new thread 
  t = Thread(target=obj.printMessage()) 
  # start the thread 
  t.start()

Note that the class has no relation to Thread, it only contains the task that will be performed by a new thread.

When creating a new thread, method to be executed is provided via Thread‘s constructor. Notice how the method is accessed using the object of class.

Thread arguments
It makes sense that the function which is executed in a separate thread takes some arguments such as a function which produces sum of integers.
In order to make this function dynamic and reusable, the integers to be added must be supplied to it as an array.

Constructor of Thread class provides an args attribute which makes it possible to supply arguments to the function.
An example would make it more clear.

from threading import * 

# function which accepts an array of numbers and prints their sum 
def get_sum(numbers): 
  sum = 0 
  for n in numbers: 
    sum+=n 
    print("Sum of numbers is",sum) 

# array of numbers 
numbers = [1,34,5,7,4] 
# create a new thread passing it the array of numbers 
t = Thread(target=get_sum, args = (numbers,)) 
# start the thread 
t.start()

Notice how the array of numbers is supplied to the args attribute when creating a new thread.
Remember that args attribute should be supplied a tuple which makes it possible to provide more than one arguments to the function which is executed as the task of the thread.
Python thread naming
Python’s threading module provides a current_thread() function which returns the reference to the current executing thread object.
Using this thread object, it is possible to set and get the name of executing thread by calling setName() and getName() methods on the thread object respectively.

from threading import * 

# task performed by thread 
def print_message(): 
  print("Task defined in separate class", current_thread().getName()) 
  # create object of class 
  for i in range(5): 
    # create a new thread 
    t = Thread(target=print_message) 
    # set name of this thread 
    t.setName("Thread" +str(i)) 
    # start the thread 
    t.start()

Above code creates a thread, sets its name using setName() method and starts it.
Then, inside the method which represents the task of this thread, getName() is used to retrieve the name of this thread and prints it producing the following output

Task defined in separate class Thread0
Task defined in separate class Thread1
Task defined in separate class Thread2
Task defined in separate class Thread3
Task defined in separate class Thread4

It is also possible to set name of a thread without calling setName() method. Thread class constructor allows you to set name of a thread by providing name attribute followed by name of the thread.

It works the same way as calling setName() method as shown below

t = Thread(target=print_name, name="Child Thread")
threading module can also be imported as import threading.
When imported this way, you need to prefix all the function calls with threading. such as threading.current_thread().getName()

Naming main thread
In every application, there is always a main thread which is executed when application is running.
If the application is multithreaded, main thread is responsible for creating new threads. Although these new threads can also spawn other threads but the origin of a thread is always main thread.

Python’s threading module provides a function main_thread() which returns a reference to main thread of program.

Using this reference, it is possible to retrieve information about main thread such as its name using getName() function as shown below

from threading import * 

print("Name of main thread is '"+
          main_thread().getName()+"'")

which prints the below output

Name of main thread is ‘MainThread’

It is also possible to customize the name of main thread by using setName() function as shown below.

from threading import * 

print("Name of main thread is '"+
            main_thread().getName()+"'") 

# change name of main thread 
main_thread().setName("Modified MainThread")
print("Updated name of main thread is '"+
            main_thread().getName()+"'")

which produces the below output

Name of main thread is ‘MainThread’
Updated name of main thread is ‘Modified MainThread’

Delaying thread execution
Sometimes you want a thread to begin execution after some amount of time or pause its execution in the middle for some time.
This can be done by using sleep() function from python’s time module. As soon as this function is called, the current executing thread pauses its execution.
This function takes an integer argument which is the number of seconds the execution needs to be held. Example,

from threading import * 
import time 

def print_name(): 
  time.sleep(5) 
  print("Will print after 5 seconds") 
  t = Thread(target=print_name, name="Child Thread") 
  t.start()

Python thread sleep() function can be used in single threaded applications as well, it is not necessary that the application should be multithreaded.
Also, it can be called inside normal functions, class methods or main thread. When called it will pause the execution of current thread for given number of seconds.

Controlling thread execution
In a multithreaded application, it might be possible that one thread is dependent on the task performed by other thread and it should not continue till the previous thread has completed.
Example,
There are two threads where one downloads a file and other reads that file. It does not make any sense for the reader thread to proceed till the downloader thread has finished downloading the file as there will be nothing to read.

In this case, the reader thread should wait till the downloader thread has completed. For such scenarios, Thread class provides join() function.
When join() is called on a thread, it causes the current executing thread to wait till the thread on which join is called completes. Example,

 

from threading import * 
import time 

def print_name(): 
  # executed in a separate thread 
  print("Delaying execution of child thread for 5 seconds") 
  time.sleep(5) 
  print("Will execute first") 

t = Thread(target=print_name) 
# start new thread 
t.start() 
# code executed in main thread 
print("Will execute after child thread")

Above code has 2 threads, a child thread and main thread.
Child thread will execute a function which waits for 5 seconds.
Main thread has some code which is written after the child thread is started.

When this code is executed, it will print the following output.

Delaying execution of child thread for 5 seconds
Will execute after child thread
Will execute first

From the output it is clear that the main thread executed before the child thread completed execution. Now suppose I want the code in main thread to be executed only after the child thread completes.

In this scenario, join() method comes out to be handy as shown in the updated code below.

from threading import * 
import time 

def print_name(): 
  # executed in a separate thread 
  print("Delaying execution of child thread for 5 seconds") 
  time.sleep(5) 

print("Will execute first") 
t = Thread(target=print_name) 
# start new thread 
t.start() 
# make current thread to wait for thread t to complete 
t.join() 
# code executed in main thread 
print("Will execute after child thread")

Output is exactly the same as we wanted.

Delaying execution of child thread for 5 seconds
Will execute first
Will execute after child thread

That is all on multithreading concept in python, how to define a thread, start a thread, using python sleep() and join()  functions with examples.