> For the complete documentation index, see [llms.txt](https://gyansetu-python.gitbook.io/python-programming/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://gyansetu-python.gitbook.io/python-programming/oops/multithreading.md).

# MultiThreading

### MultiThreading :

This article covers the basics of multithreading in Python programming language. Just like multiprocessing, multithreading is a way of achieving multitasking. In multithreading, the concept of **threads** is used.

Let us first understand the concept of **thread** in computer architecture.

**Thread**

In computing, a **process** is an instance of a computer program that is being executed. Any process has 3 basic components:

* An executable program.
* The associated data needed by the program (variables, work space, buffers, etc.)
* The execution context of the program (State of process)

A **thread** is an entity within a process that can be scheduled for execution. Also, it is the smallest unit of processing that can be performed in an OS (Operating System).

Example 1:

```python
from threading import *
from time import sleep

class bot1(Thread):
    def run(self):
        for i in range(2):
            print ("dog")
            sleep(1)

class bot2(Thread):
    def run(self):
        for i in range(2):
            print ("cat")
            sleep(1)


b1 = bot1()
b2 = bot2()

b1.start()
b2.start()

b1.join()
b2.join()

print("byeye")
```

dog&#x20;

cat

dog

cat

byeye

Example 2:

```python
# Python program to illustrate the concept 
# of threading 
# importing the threading module 
from threading import *
  
def print_cube(num): 
    """ 
    function to print cube of given num 
    """
    print("Cube: {}".format(num * num * num)) 
  
def print_square(num): 
    """ 
    function to print square of given num 
    """
    print("Square: {}".format(num * num)) 
  

    # creating thread 
t1 = Thread(target=print_square, args=(10,)) 
t2 = Thread(target=print_cube, args=(10,)) 
  
    # starting thread 1 
t1.start() 
    # starting thread 2 
t2.start() 
  
    # wait until thread 1 is completely executed 
t1.join() 
    # wait until thread 2 is completely executed 
t2.join() 
  
    # both threads completely executed 
print("Done!")
```

Square: 100

Cube: 1000

Done!

```python
#!/usr/bin/python3
from threading import *
from time import *
# Define a function for the thread
def print_time( threadName, delay):
    count = 0
    while count < 2:
        sleep(delay)
        count += 1
        print ("%s: %s" % ( threadName, ctime(time()) ))
# Create two threads as follows
try:
    t1 = Thread(target = print_time, args = ("Thread-1", 1,)) 
    t2 = Thread(target = print_time, args = ("Thread-2", 2,)) 
    t1.start() 
    
    t2.start()
    t1.join()
    t2.join()
    
    #_thread.start_new_thread( print_time, ("Thread-1", ) )
    #_thread.start_new_thread( print_time, ("Thread-2", ) )
except:
    print ("Error: unable to start thread")
```

&#x20;Thread-1: Wed Apr 29 23:21:49 2020&#x20;

Thread-2: Wed Apr 29 23:21:50 2020&#x20;

Thread-1: Wed Apr 29 23:21:50 2020&#x20;

Thread-2: Wed Apr 29 23:21:52 2020

### Creating Thread Using Threading Module

```python
#!/usr/bin/python3
from threading import *
from time import *

class myThread (Thread):
    def __init__(self, threadID, name, counter):
        Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter
    def run(self):
        print ("Starting " + self.name)
        print_time(self.name, 5, self.counter)
        print ("Exiting " + self.name)

def print_time(threadName, counter, delay):
    while counter:
        sleep(delay)
        print ("%s: %s" % (threadName, ctime(time())))
        counter -= 1

# Create new threads
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# Start new Threads
thread1.start()
thread2.start()

thread1.join()
thread2.join()

print ("Exiting Main Thread")
```

&#x20;Starting Thread-1&#x20;

Starting Thread-2

Thread-1: Tue May 21 00:11:48 2019&#x20;

Thread-1: Tue May 21 00:11:49 2019&#x20;

Thread-2: Tue May 21 00:11:50 2019&#x20;

Thread-1: Tue May 21 00:11:50 2019&#x20;

Thread-1: Tue May 21 00:11:51 2019&#x20;

Thread-2: Tue May 21 00:11:52 2019&#x20;

Thread-1: Tue May 21 00:11:52 2019&#x20;

Exiting Thread-1&#x20;

Thread-2: Tue May 21 00:11:54 2019&#x20;

Thread-2: Tue May 21 00:11:56 2019&#x20;

Thread-2: Tue May 21 00:11:58 2019&#x20;

Exiting Thread-2

Exiting Main Thread

### Synchronizing Threads :

Thread synchronization is defined as a mechanism which ensures that two or more concurrent threads do not simultaneously execute some particular program segment known as **critical section**.

Concurrent accesses to shared resource can lead to **race condition**.

> A race condition occurs when two or more threads can access shared data and they try to change it at the same time. As a result, the values of variables may be unpredictable and vary depending on the timings of context switches of the processes.

Example:

```python
import threading 

# global variable x 
x = 0

def increment(): 
	""" 
	function to increment global variable x 
	"""
	global x 
	x += 1

def thread_task(): 
	""" 
	task for thread 
	calls increment function 100000 times. 
	"""
	for _ in range(100000): 
		increment() 

def main_task(): 
	global x 
	# setting global variable x as 0 
	x = 0

	# creating threads 
	t1 = threading.Thread(target=thread_task) 
	t2 = threading.Thread(target=thread_task) 

	# start threads 
	t1.start() 
	t2.start() 

	# wait until threads finish their job 
	t1.join() 
	t2.join() 

if __name__ == "__main__": 
	for i in range(10): 
		main_task() 
		print("Iteration {0}: x = {1}".format(i,x)) 
```

Iteration 0: x = 175005&#x20;

Iteration 1: x = 200000&#x20;

Iteration 2: x = 200000&#x20;

Iteration 3: x = 169432&#x20;

Iteration 4: x = 153316&#x20;

Iteration 5: x = 200000&#x20;

Iteration 6: x = 167322&#x20;

Iteration 7: x = 200000&#x20;

Iteration 8: x = 169917&#x20;

Iteration 9: x = 153589

In above program:

* Two threads **t1** and **t2** are created in **main\_task** function and global variable **x** is set to 0.
* Each thread has a target function **thread\_task** in which **increment** function is called 100000 times.
* **increment** function will increment the global variable **x** by 1 in each call.

The expected final value of **x** is 200000 but what we get in 10 iterations of **main\_task** function is some different values.

This happens due to concurrent access of threads to the shared variable **x**. This unpredictability in value of **x** is nothing but **race condition**.

Given below is a diagram which shows how can **race condition** occur in above program:

![](/files/-M6BJBRQy0bSFUQDMmNs)

Notice that expected value of **x** in above diagram is 12 but due to race condition, it turns out to be 11!\
\
Hence, we need a tool for proper synchronization between multiple threads.

**Using Locks**

**threading** module provides a **Lock** class to deal with the race conditions. Lock is implemented using a **Semaphore** object provided by the Operating System.

Example:

```python
import threading 

# global variable x 
x = 0

def increment(): 
	""" 
	function to increment global variable x 
	"""
	global x 
	x += 1

def thread_task(lock): 
	""" 
	task for thread 
	calls increment function 100000 times. 
	"""
	for _ in range(100000): 
		lock.acquire() 
		increment() 
		lock.release() 

def main_task(): 
	global x 
	# setting global variable x as 0 
	x = 0

	# creating a lock 
	lock = threading.Lock() 

	# creating threads 
	t1 = threading.Thread(target=thread_task, args=(lock,)) 
	t2 = threading.Thread(target=thread_task, args=(lock,)) 

	# start threads 
	t1.start() 
	t2.start() 

	# wait until threads finish their job 
	t1.join() 
	t2.join() 

if __name__ == "__main__": 
	for i in range(10): 
		main_task() 
		print("Iteration {0}: x = {1}".format(i,x)) 

```

Iteration 0: x = 200000&#x20;

Iteration 1: x = 200000&#x20;

Iteration 2: x = 200000&#x20;

Iteration 3: x = 200000&#x20;

Iteration 4: x = 200000&#x20;

Iteration 5: x = 200000&#x20;

Iteration 6: x = 200000&#x20;

Iteration 7: x = 200000&#x20;

Iteration 8: x = 200000&#x20;

Iteration 9: x = 200000

![](/files/-M6BJteEuFhD5Jml6iBL)

Example:

```python
#!/usr/bin/python

from threading import *
from time import *

class myThread (Thread):
        def __init__(self, threadID, name, counter):
            Thread.__init__(self)
            self.threadID = threadID
            self.name = name
            self.counter = counter
        def run(self):
            print ("Starting " + self.name)
            # Get lock to synchronize threads
            threadLock.acquire()
            print_time(self.name, self.counter, 3)
            # Free lock to release next thread
            threadLock.release()

def print_time(threadName, delay, counter):
    while counter:
        sleep(delay)
        print ("%s: %s" % (threadName, ctime(time())))
        counter -= 1
 
threadLock = Lock()

# Create new threads
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# Start new Threads
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print ("Exiting Main Thread")
```

Starting Thread-1

Starting Thread-2

Thread-1: Thu Apr 30 21:15:40 2020&#x20;

Thread-1: Thu Apr 30 21:15:41 2020&#x20;

Thread-1: Thu Apr 30 21:15:42 2020&#x20;

Thread-2: Thu Apr 30 21:15:44 2020&#x20;

Thread-2: Thu Apr 30 21:15:46 2020&#x20;

Thread-2: Thu Apr 30 21:15:48 2020&#x20;

Exiting Main Thread
