DEV Community

Cover image for Thread synchronization in python's threading module
Atul Kushwaha
Atul Kushwaha

Posted on • Updated on

Thread synchronization in python's threading module

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 and unlocked.
  • 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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
uponthesky profile image
UponTheSky

Very clear explanation & comparison of somewhat easily confusable concepts!

Collapse
 
coderatul profile image
Atul Kushwaha

thanks for appreciating ๐Ÿซ‚