Lock Object - Thread Synchronization in Python
In multithreading when multiple threads are working simultaneously on a shared resource like a file(reading and writing data into a file), then to avoid concurrent modification error(multiple threads accessing same resource leading to inconsistent data) some sort of locking mechanism is used where in when one thread is accessing a resource it takes a lock on that resource and until it releases that lock no other thread can access the same resource.
Lock Object: Python Multithreading
In the threading
module of Python, for efficient multithreading a primitive lock is used. This lock helps us in the synchronization of two or more threads. Lock class perhaps provides the simplest synchronization primitive in Python.
Primitive lock can have two States: locked or unlocked and is initially created in unlocked state when we initialize the Lock object. It has two basic methods, acquire()
and release()
.
Following is the basic syntax for creating a Lock object:
import threading
threading.Lock()
Lock objects use two methods, they are:
acquire(blocking=True, timeout=-1)
method
This method is used to acquire the lock. When it is invoked without arguments, it blocks until the lock is unlocked.
This method can take 2 optional arguments, they are:
- blocking flag which if sent as
False
will not block the thread if the lock is acquired by some other thread already and will return False
as result. If you provide the value for this blocking flag as True
then the calling thread will be blocked if some other thread is holding the lock and once the lock is released then your thread will acquire the lock and return True
.
- timeout argument is used to provide a positive floating-point value, which specifies the number of seconds for which the calling thread will be blocked if some other thread is holding the lock right now. The default value which is -1 means the thread will be blocked for indefinite time if it cannot acquire the lock immediately.
release()
method
It is used to release an acquired lock. If the lock is locked, this method will reset it to unlocked, and return. Also this method can be called from any thread.
When this method is called, one out of the already waiting threads to acquire the lock is allowed to hold the lock.
Also, it throws a RuntimeError
if it is invoked on an unlocked lock.
Time for an Example!
Below we have a simple python program in which we have a class SharedCounter
which will act as the shared resource between our threads.
We have a task
method from which we will call the increment()
method. As more than one thread will be accessing the same counter and incrementing its value, there are chances of concurrent modification, which could lead to inconsistent value for the counter
.
Takeaways from the code above:
- When a thread acquires a lock using the
acquire()
method and then access a resource, what if during accessing the resource some error occurs? In that case, no other thread will be able to access that resource, hence we must access the resource inside the try
block. And inside the finally
block we can call the release()
method to relase the lock.
- Try commenting the code on line number 13 and 19 and try to run the code multiple times, you will see sometime the code will give the correct output but sometimes you will see the incorrect values of
counter
.