Python lock
Before understanding the concept of Python-lock we need to understand what kind if condition we require to implement the locks in Python. Let us start with multithreading and its implementation in python.
Multithreading in Python
Threads can be defined as lightweight processes that run in a certain order in order to perform a certain task. Nowadays, when computers have multiple processors it has enabled the user to run multiple threads at the same time to perform several tasks parallelly. This process of executing multiple threads at the same time is what we call multithreading.
Threading in Python
Python has a threading module, using this module we can easily implement multithreading in our program. This module enables the user not only to create threads but we can also to control and manage those threads in python.
Implementation of Threading
To implement threading in the program, the Thread module provides certain classes whose object performs specific functioning while executing these threads.
Some of the important objects in the threading module are:
Objects | Description |
Thread | It represents the execution of a single thread. |
Lock | It is the primitive lock with only two states that is Lock and Unlock. Once unlocked it cannot be reversed. |
RLock | It allows recursive locking which means we can acquire a released lock again. |
Condition | It is the phase where a lock awaits while a certain lock awaits for the other lock to perform its task. |
Event | It involves several locks waiting for a particular event to happen. |
Semaphore | An internal counter for the shared resources |
Timer | This represents the execution of the thread, along with a time constraint for which the thread requires to wait before execution. |
Barrier | This object stops the threads at a point until a required number of threads reach that point. |
Race Condition
While performing multiple threading it might be possible that multiple threads try to access and modify the same resource. If all the locks are allowed to modify the resource at the same time it would produce erroneous results. This condition where multiple threads modify the shared resource concurrently is called the Race Condition. It is necessary to prevent this to prevent the data from getting corrupted.
To avoid this situation, we need to synchronize the access and modification of the resources by various threads. This is done by using locks in python.
Primitive Lock Object
Any lock in python can either be:
- Locked
- Unlocked
For locking the lock, we use acquire() method. Once the thread is in this stage, it cannot be acquired by any other thread and the resource is limited to the particular thread until the lock is released.
To release a lock, we need to use the release() method. When we use this method on any locked lock it automatically changes its state into unlocked. If we use this method on an already unlocked lock, then it will throw an error.
acquire() method: This method is used to acquire the lock on the thread, using this method we can lock the thread until we wish to free the thread or we can pass two optional parameters specifying when to unlock the lock.
acquire(blocking=True, Timeout=-1) method: Two arguments can be passed in this method.
- blocking: if this argument is passed as False, it will not block the thread even when the lock is being acquired by any other thread, it will only be blocked if this flag is passed as True. Once set True it will acquire the lock and return True as the value otherwise it would not acquire the lock and return False as the value.
- Timeout: this argument holds the number of seconds till which you want the thread to be locked. The default value to this parameter is -1, this specifies that the lock is to be acquired for a definite time. We need to pass positive floating-point values to this parameter.
release() method: This method is used to free the lock, if the lock is locked, and the thread has been executed we release the lock with the help of using this method; so that it can be accessed by other threads in the program. It resets the lock to unlock. If this method is invoked on an already unlocked lock, this will throw a Runtime error.
RLock Objects
RLock stands for Re-entrant Lock. The problem that arise by using primitive lock was that we were not able to re-lock the thread. That means we cannot use the acquire() method on a lock twice. To solve this problem, we use RLock. The primitive lock only performs the locking without recognising that which is the thread holding the lock. It prevents unnecessary blocking from accessing the shared resources. If we have a resource that is locked using re-entrant lock, the resource can be called and accessed again without getting blocked. But unlike the primitive lock, this lock can only be released by the thread that is currently using this lock. It makes the accessing of shared resources easier and safer.
Now, let’s implement what we have learned:
import threading
import time
from random import randint
class SCnt(object):
def __init__(self, value=0):
self.lock = threading.Lock()
self.counter = value
def increment(self):
print("Wait while the lock is being acquired")
self.lock.acquire()
try:
print('lock is acquired, counter value: ', self.counter)
self.counter = self.counter + 1
finally:
print('lock is freed, counter value: ', self.counter)
self.lock.release()
def task(c):
# Choose a random number
random_no = randint(1, 5)
# performing increment on the random numbers
for i in range(random_no):
c.increment()
print('Done')
if __name__ == '__main__':
sCounter = SCnt()
task1 = threading.Thread(target=task, args=(sCounter,))
task1.start()
task2 = threading.Thread(target=task, args=(sCounter,))
task2.start()
print('Wait for the worker threads to get loaded')
task1.join()
task2.join()
print('Counter:', sCounter.counter)
In this Program, we have created a class SCnt that is being shared among various threads. The task method we have called the increment() function will access the same counter and will increment the value of the counter. The value of the counter is inconsistent due to the concurrent modifications that are being performed on it.