As we saw ๐๏ธ๐๏ธ in our previous blogs, threading is susceptible to race conditions. To tackle race conditions, we may use locks and semaphores to manage synchronization and data consistency while using threads
There are 3 major techniques for thread synchronization
- Lock ๐
- Rlock ยฎ๏ธ๐
- Semaphore, BoundedSemaphore
Lock
- A Lock is a basic
synchronization primitive
that provides exclusive access to a shared resource. - It has two states:
locked
andunlocked
. - Only one thread can acquire the lock at a time. If another thread tries to acquire a locked lock, it will be blocked until the lock is released.
- It is not reentrant, meaning that a thread holding the lock cannot acquire it again without releasing it first.
with
context manager, manages acquiring and releasing lock for us, other wise we can use lock.acquire() and lock.release() methods as well
from threading import Lock
lock = Lock()
def example_function():
with lock:
# critical section
pass
RLock (Reentrant Lock):
- An RLock is an extension of the basic Lock that allows a thread to acquire the
lock multiple times
(reentrant). - A thread that already holds the lock can acquire it again without blocking, but it must release the lock the same number of times it acquired it.
- Useful in situations where a function might need to acquire the same lock recursively.
from threading import RLock
rlock = RLock()
def example_function():
with rlock:
# critical section
pass
Semaphore
- A Semaphore is a more
generalized synchronization primitive
that allows a specified number of threads to access a shared resource concurrently. - It maintains a counter that is decremented each time a thread acquires it and incremented when the thread releases it.
- Useful for controlling access to a resource with limited capacity.
from threading import Semaphore
semaphore = Semaphore(3) # Allow up to 3 threads to access the resource concurrently
def example_function():
with semaphore:
# critical section
pass
BoundedSemaphore
- A BoundedSemaphore is similar to Semaphore but has an
upper limit
on its counter. - It raises a ValueError if the counter exceeds the specified bound when incremented.
from threading import BoundedSemaphore
bounded_semaphore = BoundedSemaphore(3) # Bound the semaphore to allow up to 3 threads
def example_function():
with bounded_semaphore:
# critical section
pass
conclusion
- In summary, Lock and RLock provide exclusive access to a shared resource, while Semaphore and BoundedSemaphore allow a specified number of threads to access a shared resource concurrently. The choice between them depends on the synchronization requirements of your multithreaded application
Top comments (2)
Very clear explanation & comparison of somewhat easily confusable concepts!
thanks for appreciating ๐ซ