Python | Locking without Deadlocks

This article focusses on dealing how to get more than one lock at a time if a multithread program is given along with avoiding the deadlocks.

Multithread programs – Due to the threads that keep on attempting to get multiple locks at once, these are very much prone to deadlocks. Understanding it with an example – a lock is already been acquired by a thread and then a second lock is attempted by the block then in that case, the program can freeze as the thread can potentially block the progress of other threads.

Solution:



  • Enforcement of ordering rule
  • Assigning each lock in a unique manner to the program.
  • Only allowing multiple locks to be acquired in ascending order.

Code #1 : Implementing the solution uisng a context manager.

filter_none

edit
close

play_arrow

link
brightness_4
code

# importing libraries
import threading
from contextlib import contextmanager
  
# threading to stored information
_local = threading.local()
  
@contextmanager
def acquire(*lock_state_state):
      
    # Object identifier to sort the lock
    lock_state_state = sorted(lock_state_state, key = lambda a: id(a))
      
    # checking the validity of previous locks
    acquired = getattr(_local, 'acquired', [])
    if acquired and max(id(lock_state) for 
                        lock_state in acquired) >= id(lock_state_state[0]):
        raise RuntimeError('lock_state Order Violation')
          
    # Collecting all the lock state. 
    acquired.extend(lock_state_state)
    _local.acquired = acquired
      
    try:
    for lock_state in lock_state_state:
        lock.acquire()
    yield
    finally:
          
        # locks are released in reverse order. 
        for lock_state in reversed(lock_state_state):
            lock_state.release()
        del acquired[-len(lock_state_state):]

chevron_right


Locks are acquired in the normal way using the context manager and for performing this task acquire() function is used as there was more than one lock as shown in the code below :

Code #2 :

filter_none

edit
close

play_arrow

link
brightness_4
code

# threads
import threading
  
# creating locks
lock_state_1 = threading.Lock()
lock_state_2 = threading.Lock()
  
# using acquire as there are more than one lock
def thread_1():
    while True:
        with acquire(lock_state_1, lock_state_2):
            print('Thread-1')
  
def thread_2():
    while True:
        with acquire(lock_state_2, lock_state_1):
            print('Thread-2')
              
t1 = threading.Thread(target = thread_1)
  
# daemon thread runs without blocking
# the main program from exiting
t1.daemon = True
t1.start()
  
t2 = threading.Thread(target = thread_2)
t2.daemon = True
t2.start()

chevron_right


  • Even after the acquisition of the locks specification in a different order in each function – the program will run forever without deadlock.
  • Sorting the locks plays an important role according to the object identifier as locks after being sorted get acquired in a consistent manner regardless of how the user might have provided them to acquire().
  • If multiple threads are nested as shown in the code below, to solve a subtle problem with detection potential deadlock, thread-local storage is used.

Code #3 :

filter_none

edit
close

play_arrow

link
brightness_4
code

# threads
import threading
  
# creating locks
lock_state_1 = threading.Lock()
lock_state_2 = threading.Lock()
def thread_1():
      
    while True:
    with acquire(lock_state_1):
        with acquire(lock_state_2):
            print('Thread-1')
              
def thread_2():
    while True:
        with acquire(lock_state_2):
            with acquire(lock_state_1):
                print('Thread-2')
                  
t1 = threading.Thread(target=thread_1)
# daemon thread runs without blocking
# the main program from exiting
t1.daemon = True
t1.start()
  
t2 = threading.Thread(target=thread_2)
t2.daemon = True
t2.start()

chevron_right


On running this version of the program, one of the threads will crash with an exception such as :

Exception in thread Thread-1:
Traceback (most recent call last):
    File "/usr/HP/lib/python3.3/threading.py", line 639, in _bootstrap_inner
        self.run()
    File "/usr/HP/lib/python3.3/threading.py", line 596, in run
        self._target(*self._args, **self._kwargs)
    File "deadlock.py", line 49, in thread_1
        with acquire(y_lock):
    File "/usr/HP/lib/python3.3/contextlib.py", line 48, in __enter__
        return next(self.gen)
    File "deadlock.py", line 17, in acquire
        raise RuntimeError("Lock Order Violation")
RuntimeError: Lock Order Violation

Each thread remembers the lock been already acquired that’s why it’s been showing this error. The ordering constraints that acquired the locks is also enforced and list of previously acquired locks is checked by the acquire() method.




My Personal Notes arrow_drop_up

Check out this Author's contributed articles.

If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

Please Improve this article if you find anything incorrect by clicking on the "Improve Article" button below.


Article Tags :

Be the First to upvote.


Please write to us at contribute@geeksforgeeks.org to report any issue with the above content.