Thread Synchronization using Locks and Semaphores

Python Thread Synchronization using Locks and Semaphores

In this post, we will discuss Thread Synchronization using Locks and Semaphores.

Thread Synchronization or Thread Safe 

The ‘Thread Synchronization’ or ‘Thread Safe’ is when you are running a thread on a particular object and if you prevent other threads from performing on the same object. The object on which the threads are synchronized is called ‘mutex’ (mutually exclusive lock) or a ‘synchronized object’. We follow thread synchronization when several threads are performing on the same object at the same time. We can achieve thread synchronization using the following practices. 

  1. Locks 
  2. Semaphores

Locks in Python Threads 

We can use Locks to add a lock on the object on which a particular thread is performing. Using locks, you can make a thread to lock an object and prevent other threads from accessing the object until the current thread’s execution gets completed. 

Consider a toilet that can accommodate only one person. If a person has entered this toilet, the next person can enter only after the first person comes out. Similarly, the threads can also lock object and prevent other threads from accessing its current object. In such cases, the lock of the object is mutually locked from both the sides. This locked object is called ‘mutex’ (mutually exclusive lock). 

Python thread synchroniation using Locks-1

We can create a lock using the format mentioned below.

l=Lock()

We can add the created lock to an object using the format mentioned below.

l.acquire()

We can unlock or release the object using the format mentioned below.

l.release()

Let us now look at a program to attain thread synchronization using locks.

#Python program to show thread synchronization using locks

#import threading and time modules
from threading import *
from time import *

class RoadTransport:
  #constructor that accepts number of available seats
  def __init__(self,available):
    self.available = available
    #create a lock object
    self.l = Lock()

  #a method to reserve the seats
  def reserve(self, required):
    #lock the current object
    self.l.acquire()
    #display the number of available seats
    print("Available seats: ",self.available)
    #find the name of the thread
    name = current_thread().getName()

    #if available>required, provide the seat
    if(self.available>=required):
      #display the seat is provided for the person
      print("%d seats have been alloted for %s"%(required,name))
      #create a time delay for the ticket printing
      sleep(3)
      #decrease the number of available seats
      self.available-=required
    else:
      #this gets executed if there are no available seats
      #or if the required seats are higher than the available seats
      print("Seats full. Please check another bus, {}".format(name))
      #release the lock
    self.l.release()

#create instance to the RoadTransport class
#mention only one seat is available
object1 = RoadTransport(1)

#create two threads and mention 1 seat is needed
t1 = Thread(target=object1.reserve, args=(1,))
t2 = Thread(target=object1.reserve,args=(1,))

#provide names to the threads
t1.setName('First Passenger')
t2.setName('Second Passenger')

#run the threads
t1.start()
t2.start()
Output:

Available seats:  1
1 seats have been alloted for First Passenger
Available seats:  0
Seats full. Please check another bus, Second Passenger

In the above program, we imported the threading and time modules.

We created a constructor method that accepts number of available seats. We also created a method called reserve() in the class that accepts the number of required seats as an argument.

We added a logic using the ‘if’ statement to provide seats only based on the available seats. After this, we created two threads and named each thread using the setName() method and started both the threads.

The first thread for the ‘First passenger’ successfully booked the seat and updated the available seats to zero. Since no seats are available, the second passenger was not able to book the seats. The thread related to the second passenger started execution only after the thread related to the first passenger was released.

We used the acquire() and release() methods to add lock to a thread and release lock from the same.

Semaphores in Python Threads

A semaphore is an object in Python that offers Thread Synchronization based on a counter. Please refer to the format below to create a semaphore.

l=Semaphore(countervalue) #the default counter value is 1

If you do not enter any counter value, the default counter value gets set to 1. When we use the acquire() method after creating a semaphore, the counter gets reduced by 1 and when we call the release() method, the same value gets incremented by 1.

We can rewrite the same program mentioned above using a semaphore by replacing the self.l = Lock() with self.l = Semaphore. Let us have a look at the implementation of the same program using Semaphores now.

#Python program to show thread synchronization using semaphores

#import threading and time modules
from threading import *
from time import *

class RoadTransport:
  #constructor that accepts number of available seats
  def __init__(self,available):
    self.available = available
    #create a lock object
    self.l = Semaphore()

  #a method to reserve the seats
  def reserve(self, required):
    #lock the current object
    self.l.acquire()
    #display the number of available seats
    print("Available seats: ",self.available)
    #find the name of the thread
    name = current_thread().getName()

    #if available>required, provide the seat
    if(self.available>=required):
      #display the seat is provided for the person
      print("%d seats have been alloted for %s"%(required,name))
      #create a time delay for the ticket printing
      sleep(3)
      #decrease the number of available seats
      self.available-=required
    else:
      #this gets executed if there are no available seats
      #or if the required seats are higher than the available seats
      print("Seats full. Please check another bus, {}".format(name))
    #release the lock
    self.l.release()

#create instance to the RoadTransport class
#mention only one seat is available
object1 = RoadTransport(1)

#create two threads and mention 1 seat is needed
t1 = Thread(target=object1.reserve, args=(1,))
t2 = Thread(target=object1.reserve,args=(1,))

#provide names to the threads
t1.setName('First Passenger')
t2.setName('Second Passenger')

#run the threads
t1.start()
t2.start()
Output

Available seats:  1
1 seats have been alloted for First Passenger
Available seats:  0
Seats full. Please check another bus, Second Passenger

You can notice in the program above that we implemented the same concept to assign tickets based on the availability using Semaphores instead of locks.

Scroll to Top