Topics

Condition Object

Welcome to another tutorial, here you will learn about Thread Synchronization on object conditioning in Python. 

During programming to synchronize to gain access to any resources efficiently, thus you can associate a condition with tasks for any thread to wait until a certain condition is met, or to notify other threads about the condition being fulfilled, in order for them to unblock themselves.

Have a look at this illustration base on a Producer-Consumer issue. Suppose a certain producer produces a few items and a certain consumer consumes them, as such, it is until the Producer has produced the item that the Consumer can consume it. Therefore, the consumer has to wait till the producer produces an item; on the other hand, it is the duty of the Producer to inform the Consumer that an item is available for consumption once it is produced successfully.

And if multiple Consumers are consuming the item produced by the Producer then the Producer must inform all the Consumers about the new item produced.

In Python. the illustration above can be seen as perfect for the condition object in multithreading.

 

Condition object: wait(), notify() and notifyAll()

Below is the general syntax for the condition object.

condition = threading.Condition([lock])

The condition object needs an optional lock object as an input argument.

In addition, the condition object has the acquire() and release() methods that call the corresponding methods of the associated lock. Also, it has the methods wait(), notify() and notifyAll(). However, three methods must be called only after the calling thread has acquired the lock.

 

Condition class methods

The condition class methods are highlighted below:

 

acquire(*args) method

This method is typically used to acquire the lock. It calls the corresponding to acquire() method on the underlying lock present in the condition object, and the return value is whatever that method returns.

 

release() method

The release() method is primarily used to release the lock. It calls the corresponding release() method on the underlying lock exiting in the condition object.

 

wait([timeout]) method

In this case, The method is used to block the thread and make it wait until another thread notifies it by calling the notify() or notifyAll() method on the same condition object or until the timeout occurs. In addition, this method must be called only when the calling thread has acquired the lock.

However, when it is called, it releases the lock and then blocks the thread until it is awakened by a notify() or notifyAll() call for the same condition variable from some other thread, or whenever the timeout happens.

 

notify() method

This method is used to wake up any thread waiting on the corresponding condition. It must be called only when the calling thread has acquired the lock. But, calling this method will wake one waiting thread only.

 

notifyAll() method

This type of method wakes up all the threads waiting on the given condition. It typically acts like notify() method, but wakes up all the waiting threads rather than one.

 

Example of Condition Object

From this example, we implemented a simple producer-consumer solution, in which the producer produces an item and adds it to a list from which the consumer is consuming the items.

import threading
import time
from random import randint

class SomeItem:
  # init method
  def __init__(self):
    # initialize empty list
    self.list = []
  
  # add to list method for producer
  def produce(self, item):
    print("Adding item to list...")
    self.list.append(item)
    
  # remove item from list method for consumer
  def consume(self):
    print("consuming item from list...")
    item = self.list[0]
    print("Item consumed: ", item)
    self.list.remove(item)

def producer(si, cond):
    r = randint(1,5)
    # creating random number of items
    for i in range(1, r):
      print("working on item creation, it will take: " + str(i) + " seconds")
      time.sleep(i)
      print("acquiring lock...")
      cond.acquire()
      try:
        si.produce(i)
        cond.notify()
      finally:
        cond.release()
      
def consumer(si, cond):
    cond.acquire()
    while True:
      try:
        si.consume()
      except:
        print("No item to consume...")
        # wait with a maximum timeout of 10 sec
        val = cond.wait(10)
        if val:
          print("notification received about item production...")
          continue
        else:
          print("waiting timeout...")
          break
        
    cond.release()
    
if __name__=='__main__':
  # condition object
  cond = threading.Condition()
  # SomeItem object
  si = SomeItem()
  # producer thread
  p = threading.Thread(target=producer, args=(si,cond,))
  p.start()
  # consumer thread
  c = threading.Thread(target=consumer, args=(si,cond,))
  c.start()

  #print('Waiting for producer and consumer threads...')
  p.join()
  c.join()
  print("Done")  

Output:

working on item creation, it will take: 1 secondsconsuming item from list...
No item to consume...

acquiring lock...
Adding item to list...
working on item creation, it will take: 2 seconds
notification received about item production...
consuming item from list...
Item consumed:  1
consuming item from list...
No item to consume...
acquiring lock...
Adding item to list...
working on item creation, it will take: 3 seconds
notification received about item production...
consuming item from list...
Item consumed:  2
consuming item from list...
No item to consume...
acquiring lock...
Adding item to list...
notification received about item production...
consuming item from list...
Item consumed:  3
consuming item from list...
No item to consume...
waiting timeout...
Done

Note the following important things.

  • We created a class SomeItem that has a list that acts as the shared resource between the producer and consumer thread.
  • Also, our producer thread is randomly generating some list items and adding them to the list.
  • Then, the consumer thread was to consume the item, if an item is not found, it starts to wait. Now, when the producer sends a notification to the consumer about the item creation before its timeout, then the consumer consumes the item, otherwise, it leaves due to timeout.