Search code examples
pythonmultithreadingdeadlock

Question about deadlock simulation in python


When I tried to implement a python code to simulate a deadlock, I encountered some interesting questions:

1) I used the following code to simulate deadlock.

  1 from threading import *
  2 import time
  3
  4
  5 def thread_one(lock1, lock2):
  6     print("thread 1 is trying to acquire lock 1")
  7     lock1.acquire()
  8     print("lock1 acquired by thread 1")
  9     time.sleep(1)
 10     print("thread 1 is trying to acquire lock 2")
 11     lock2.acquire()
 12
 13
 14 def thread_two(lock1, lock2):
 15     print("thread 2 is trying to acquire lock 2")
 16     lock2.acquire()
 17     print("lock2 acquired by thread 2")
 18     time.sleep(1)
 19     print("thread 2 is trying to acquire lock 1")
 20     lock1.acquire()
 21
 22
 23 if __name__ == "__main__":
 24     lock1 = Lock()
 25     lock2 = Lock()
 26
 27     t1 = Thread(target=thread_one, args=(lock1, lock2))
 28     t2 = Thread(target=thread_two, args=(lock1, lock2))
 29
 30     t1.start()
 31     t2.start()
 32
 33     t1.join()
 34     t2.join()

And these are my outputs:

thread 1 is trying to acquire lock 1
lock1 acquired by thread 1
thread 2 is trying to acquire lock 2
lock2 acquired by thread 2
thread 1 is trying to acquire lock 2
thread 2 is trying to acquire lock 1
(program stuck here)

Please correct me if I am wrong. I think my simulation is correct and the two threads are stuck at the step of acquiring the 2nd lock.

2) Then I made the following changes:

 10     print("thread 1 is trying to release lock 2")
 11     lock2.release()

 28     t2 = Thread(target=thread_one, args=(lock1, lock2))

Basically, I want both thread instances to run the same function thread_one and in the function it trie to release lock2, which was not acquired yet. Then I got these outputs:

thread 1 is trying to acquire lock 1
lock1 acquired by thread 1
thread 1 is trying to acquire lock 1
thread 1 is trying to release lock 2
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/Users/bawang/.pyenv/versions/3.6.5/lib/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "/Users/bawang/.pyenv/versions/3.6.5/lib/python3.6/threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "test.py", line 11, in thread_one
    lock2.release()
RuntimeError: release unlocked lock
(program stuck here)

My question is: Why do both of the threads get hung there (I need to ctrl+c twice to cancel both of them)? I understand the 2nd thread is waiting for lock1 to be released by the 1st thread. However, why does the first thread gets stuck after throwing the exception?

3) Then I moved a bit further by making the following changes:

  5 def thread_one(lock1, lock2):
  6     print("thread 1 is trying to acquire lock 1")
  7     lock1.acquire()
  8     print("lock1 acquired by thread 1")
  9     time.sleep(1)
 10     print("thread 1 is trying to release lock 2")
 11     lock2.release()
 12
 13
 14 def thread_two(lock1, lock2):
 15     print("thread 2 is trying to acquire lock 2")
 16     lock2.acquire()
 17     print("lock2 acquired by thread 2")
 18     time.sleep(1)
 19     print("thread 2 is trying to release lock 1")
 20     lock1.release()

 27     t1 = Thread(target=thread_one, args=(lock1, lock2))
 28     t2 = Thread(target=thread_two, args=(lock1, lock2))

This time I want to see what happens if lock1&lock2 are acquired by one thread while released in another. These are my outputs:

thread 1 is trying to acquire lock 1
lock1 acquired by thread 1
thread 2 is trying to acquire lock 2
lock2 acquired by thread 2
thread 1 is trying to release lock 2
thread 2 is trying to release lock 1
(Program completes)

My question is that why are there no exceptions? I did expect two RuntimeError: release unlocked lock in this case.


Solution

  • 1) Yes. You are correct.

    2) As you guessed, the 1st thread has stopped, but you have two more threads: the 2nd thread and the main thread. The first cntl + c kills the main thread. You can check the message for KeybboardInterrupt. The first one happens at t2.join().

    3) Both threads are acquired and released correctly. Different threads can acquire the same lock. Therefore, thread 1 simply released lock 2 that was acquired by thread 2, and vice versa.