In this post, we will discuss Python Thread communication using notify(), wait() and queue() in detail.
Python Thread Communication
You will undoubtedly come across cases where two or more threads should communicate with each other. For example, we can have a customer thread waiting for the data of items from the shopkeeper thread, as shown in the program below. The classes in the program below communicate through the objects it creates and a boolean variable, dataOfItems.
When the dataOfItems is set to False, the Customer sleeps for 100 milliseconds and then checks whether it is set to True or False. Using this logic, we can show that the Shopkeeper is producing the items. And every time an item gets produced completely, we also display a message that the ‘Item has been produced’.
#Python program to understand thread communication
from math import prod
from threading import *
from time import *
#create the shopkeeper class
class ShopKeeper:
def __init__(self):
self.lst = []
self.dataOfItems = False
def listItems(self):
#create 3 items and add to the list
for i in range(1,4):
self.lst.append(i)
sleep(1)
print("Item has been produced")
#let the Customer know that the data of items are available now
self.dataOfItems = True
#create Customer class
class Customer:
def __init__(self,shop):
self.Shop = shop
def getItems(self):
#sleep for 100 ms for each iteration where dataOfItems is False
while self.Shop.dataOfItems == False:
sleep(0.1)
#display the items from the list when data is completely produced
print(self.Shop.lst)
#create Shopkeeper object
s = ShopKeeper()
#create the customer object and pass shopkeeper object
cust = Customer(s)
#create threads for Shopkeeper and Customer threads
t1 = Thread(target=s.listItems)
t2 = Thread(target=cust.getItems)
#run the threads
t1.start()
t2.start()
Output:
[1]
[1]
[1]
[1]
[1]
[1]
[1]
[1]
[1]
Item has been produced
[1, 2]
[1, 2]
[1, 2]
[1, 2]
[1, 2]
[1, 2]
[1, 2]
[1, 2]
[1, 2]
[1, 2]
Item has been produced
[1, 2, 3]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3]
[1, 2, 3]
Item has been produced
[1, 2, 3]
We can see that the Shopkeeper and Customer classes communicate with each other in the program mentioned above. However, this is not the right way to communicate because the Customer goes into a sleep state for 100 milliseconds and finds whether the boolean variable is True or not only after that time. There could be higher chances for the production of the list to get completed before that time. It means that you are looking at a time delay between 1 and 99 milliseconds to obtain data after its production is completed.
To make the communication between the threads much better, Python offers two fundamental ways.
- wait() and notify() methods
- queue()
Thread communication using notify() and wait() methods
We use the Condition class of the threading module to enhance the communication between threads. We can create an object for the condition class and set it to a variable, as shown below.
cv = Condition()
The Condition class has methods that we can use in thread communication. For instance, we can use the notify() method to inform other threads that waiting is no longer required. We also have the notify_all() method that can notify all the threads that are waiting for a particular thread to complete its execution.
Likewise, we also have a wait() method that we can use in any thread to let it wait. However, this wait action gets terminated once the thread with the wait() method is notified by another thread using the notify() method.
It means that we can use the methods such as notify(), notify_all(), and wait() to make the communication between threads much better. It also means that we will not have to use any boolean variable as we did in the first program of this post. Let us find out how we can make the first program of this post better this time using the notify() and wait() methods.
"""Python program to show the thread communication
using the Condition object"""
from socket import timeout
from threading import *
from time import *
#create the Shopkeeper class
class ShopKeeper:
def __init__(self):
self.lst = []
self.cv = Condition()
def listItems(self):
#locking the condition object
self.cv.acquire()
#create 1 to 10 items and add to the list
for i in range(1,4):
self.lst.append(i)
sleep(1)
print("Item has been produced")
#notify the customer that items have been produced
self.cv.notify()
#release the lock
self.cv.release()
#create Customer class
class Customer:
def __init__(self,shop):
self.shop = shop
def getItems(self):
#obtain lock on condition object
self.shop.cv.acquire()
#wait only for 0 seconds after getting notified by Shopkeeper class
self.shop.cv.wait(timeout=0)
#release the lock
self.shop.cv.release()
#display the items from the list
print(self.shop.lst)
#create Shopkeeper object
s = ShopKeeper()
#create an object for Customer and pass the Shopkeeper's object
c = Customer(s)
#create threads for Shopkeeper and Customer
t1 = Thread(target=s.listItems)
t2 = Thread(target=c.getItems)
#run the threads
t1.start()
t2.start()
Output:
Item has been produced
Item has been produced
Item has been produced
[1, 2, 3]
Thread Communication using Queue
By taking the same example of Shopkeeper and Customer again, but using the queue module’s Queue class, you can generate a queue that takes the data produced by the Shopkeeper. The Customer can use the same data from the queue.
Suppose you are familiar with the typical behaviours in the world of programming. In that case, you probably know that a Queue follows the structure of FIFO (First In, First Out), where the data is inserted from one side and taken out from the other side one by one. This concept of Queues is essential when several shopkeepers want to communicate with their customers simultaneously in the real world.
Let us make the Shopkeeper use the put() method to insert items into the queue, and the Customer use the get() method to obtain items from the queue.
#thread communication using the queue
from threading import *
from time import *
from queue import *
#create Shopkeeper class
class Shopkeeper:
def __init__(self):
self.q = Queue()
def produce(self):
#create 1 to 10 items and add them to the queue
for i in range(1,11):
print('Producing item: ',i)
self.q.put(i)
sleep(1)
#create the Consumer class
class Customer:
def __init__(self,shop):
self.shop = shop
def consume(self):
#retrieve the items from the queue
for i in range(1,11):
print('Retrieving item: ',self.shop.q.get(i))
#Create the shopkeeper object
s = Shopkeeper()
#Create the Customer object and pass the Shopkeeper's object 's'
c = Customer(s)
#Create threads of Shopkeeper and Customer respectively
t1 = Thread(target=s.produce)
t2 = Thread(target=c.consume)
#run the threads
t1.start()
t2.start()
Output:
Producing item: 1
Retrieving item: 1
Producing item: 2
Retrieving item: 2
Producing item: 3
Retrieving item: 3
Producing item: 4
Retrieving item: 4
Producing item: 5
Retrieving item: 5
Producing item: 6
Retrieving item: 6
Producing item: 7
Retrieving item: 7
Producing item: 8
Retrieving item: 8
Producing item: 9
Retrieving item: 9
Producing item: 10
Retrieving item: 10
In the program mentioned above, the Shopkeeper class places an item into the queue using the put() method. The Customer class’s object receives the item instantly from the queue using the get() method.
We hope you have understood the concept of Python Thread Communication using the notify(), wait() and queue(). Please share your views or queries about the same in the comments section below if you have any. We will get back to you.