Synchronization in Python

Synchronization in Python with Examples

In this article, I am going to discuss Synchronization in Python with examples. Please read our previous article where we discussed Daemon threads in Python. As part of this article, we are going to discuss the following pointers in detail.

  1. Synchronization in Python
  2. How to overcome data inconsistency problems?
  3. How to implement synchronization in python?
  4. Synchronization By using Lock concept in python
  5. Problem with Simple Lock in Python
  6. Synchronization By using RLock concept in Python
  7. Difference between Lock and RLock
  8. Synchronization by using Semaphore in Python
  9. Bounded Semaphore in Python
Synchronization in Python:

If multiple threads are executing simultaneously on object or data then there may be a chance of data inconsistency problems.

Let’s understand it through an example:

Consider a couple who is having a Joint account and both are having their ATM cards. They come to different ATMs and try to withdraw some amount at the same time. Let’s say the total balance in the account is 500 and Wife tries to withdraw 450 and the husband tries to withdraw 100. When they swipe the card for withdrawing money, the balance shown will be 500. Two threads will be created for the transaction, out of which only one thread should be successful and the other should fail. If both the threads get successful then its a loss to the bank. So, the threads should be in synchronization so that one fails and the other wins.

Let us consider another example:

Consider an online bus booking website, being accessed by two users at the same time to book tickets in the same bus and route. Assume that there are only three seats left and both of them trying to book those at the same time. Only one should be able to proceed and others should fail.

Understandings

In both the examples, trouble arises when different threads try to work on the same data at same time. To avoid such troubles and process the threads safely without any problems, the code synchronization should be implemented which restricts multiple threads to work on the same code at the same time.

Program: Inconsistent data (demo18.py)
from threading import *
import time

def wish(name,age):
   for i in range(3):
       print("Hi",name)
       time.sleep(2)
       print("Your age is",age)

t1=Thread(target=wish, args=("Sireesh",15))
t2=Thread(target=wish, args=("Nitya",20))

t1.start()
t2.start()

Output:

Synchronization in Python with Examples

My requirement here is to print the ‘hi’ message and the age of that person at the same time. But since both the threads are executing simultaneously we are getting an irregular output, hence making it difficult to know the age of Sireesh and Nitya.

How to overcome data inconsistency problems?

We can solve these inconsistency problems by synchronizing the threads such that they will be executed one by one. The main application areas of synchronization are,

  1. Online Reservation system
  2. Funds Transfer from joint accounts etc.
How to implement synchronization in python?

In Python, we can implement synchronization by using the following concepts

  1. Lock
  2. RLock
  3. Semaphore
Synchronization By using Lock concept in python:

Locks are the most fundamental synchronization mechanism provided by the threading module. We can create Lock object as follows,

l=Lock()

The Lock object can be held by only one thread at a time. If any other thread wants the same lock then it will have to wait until the other one releases it. It’s similar to waiting in line to book a train ticket, public telephone booth etc.

acquire() method: A Thread can acquire the lock by using acquire() method
l.acquire()
release() method: A Thread can release the lock by using release() method.
l.release()

Note: Only the thread currently holding the lock is allowed to call the release() method thread, otherwise we will get Runtime Error saying, RuntimeError: release unlocked lock

Program: lock acquiring and releasing in python (demo19.py)

from threading import *
l=Lock()
l.acquire()
print("lock acquired")
l.release()
print("lock released")

Output:

Synchronization By using Lock concept in python

Program: Synchronization using lock acquiring and releasing in python (demo20.py)
from threading import *
l=Lock()
l.release()
print("lock released")

Output: Synchronization using lock acquiring and releasing in python

Program: Synchronization By using Lock concept in Python (demo21.py)

from threading import *
import time
l=Lock()
def wish(name,age):
   for i in range(3):
       l.acquire()
       print("Hi",name)
       time.sleep(2)
       print("Your age is",age)
       l.release()

t1=Thread(target=wish, args=("Sireesh",15))
t2=Thread(target=wish, args=("Nitya",20))

t1.start()
t2.start()

Output:

Synchronization By using Lock concept in Python

Problem with Simple Lock in Python:

The standard lock object does not care which thread is currently holding that lock. If the lock is being held by one thread, and if any other thread tries to accquire the lock, then it will be blocked, even if it’s the same thread that is already holding the lock.

So, if the Thread calls recursive functions or nested access to resources, then the thread may try to acquire the same lock again and again, which may result in blocking of our thread. Hence Traditional Locking mechanism won’t work for executing recursive functions.

Synchronization By using RLock concept in Python:

To overcome the above problem of Simple Lock, we should go for RLock(Reentrant Lock). Reentrant means the thread can acquire the same lock again and again. This will block the thread only if the lock is held by any other thread. Reentrant facility is available only for owner thread but not for other threads.

This RLock keeps track of recursion level and hence for every acquire() there should be a release() call available.

The number of acquire() calls and release() calls should be matched then for the lock to be released i.e if there are two accquire calls then there should be two release calls for the lock to be released. If there is only one release call for two accquire calls then the lock wont be released.

Program: Synchronization By using RLock concept in Python (demo22.py)

from threading import *
import time
l=RLock()
def factorial(n):
   l.acquire()
   if n==0:
       result=1
   else:
       result=n*factorial(n-1)
   l.release()
   return result

def results(n):
   print("The Factorial of", n, "is:", factorial(n))

t1=Thread(target=results, args=(5,))
t2=Thread(target=results, args=(9,))

t1.start()
t2.start()

Output:

Synchronization By using RLock concept in Python

Difference between Lock and RLock in Python:

Difference between Lock and RLock in Python

Synchronization by using Semaphore in Python:

Semaphore is advanced Synchronization Mechanism. Sometimes there might be requirements where at a time a particular number of threads should be allowed to access the resources. Like, at a time 10 members are allowed to access the database server or at a time 4 members are allowed to access Network connection. We can’t handle this requirement, using Lock and RLock concepts. Hence, we should go for the Semaphore concept in such cases.

Creating a Semaphore object

We can create a Semaphore object as follows.

s=Semaphore(counter)

Here counter represents the maximum number of threads are allowed to access simultaneously. The default value of counter is 1.

Whenever thread executes the acquire() method then the counter value will be decremented by 1 and if the thread executes the release() method then the counter value will be incremented by 1.

Program: Synchronization By using Semaphore in Python (demo23.py)
from threading import *
import time
s=Semaphore(2)
def wish(name,age):
  for i in range(3):
      s.acquire()
      print("Hi",name)
      time.sleep(2)
      s.release()
t1=Thread(target=wish, args=("Sireesh",15))
t2=Thread(target=wish, args=("Nitya",20))
t3=Thread(target=wish, args=("Shiva",16))
t4=Thread(target=wish, args=("Ajay",25))
t1.start()
t2.start()
t3.start()
t4.start()

Output:

Synchronization By using Semaphore in Python

In the above program, since the counter for Semaphore is 2, the threads t1 and t2 will be executed parallely and after their execution is completed then the other threads t3 and t4 will start.

Bounded Semaphore in Python:

In Normal Semaphore, discussed above, the release method can be called any number of times to increase the counter, irrespective of the acquire method. Sometimes, the number of release() calls can exceed the number of acquire() calls also.

Program: Bounded Semaphore in Python (demo24.py)

from threading import *
s=Semaphore(2)

s.acquire()
s.acquire()
s.release()
s.release()
s.release()
s.release()
print("End")

Output: Bounded Semaphore in Python

It is valid because in normal semaphore we can call release() any number of times. This may result in programming errors or may raise confusions. So, it is always recommended to use Bounded Semaphore which raises an error if the number of release() calls exceeds the number of acquire() calls.

Program: Bounded Semaphore in Python (demo25.py)
from threading import *
s=BoundedSemaphore(2)
s.acquire()
s.acquire()
s.release()
s.release()
s.release()
s.release()
print("End")

Output: Bounded Semaphore in Python

Conclusion:

The major idea of synchronization is to overcome data inconsistency problems. But the disadvantage of synchronization is it increases waiting time of threads and creates performance problems. Hence it is recommended to use synchronization only if the requirement demands.

In the next article, I am going to discuss Inter Thread communication in Python with Examples. Here, in this article, I try to explain Synchronization in Python with Examples. I hope you enjoy this Synchronization in Python with Examples article. I would like to have your feedback. Please post your feedback, question, or comments about this article.

Leave a Reply

Your email address will not be published.