Understanding Thread Deadlock in Python
Learn about thread deadlock in Python, a concurrency issue where threads become stuck waiting for conditions that never occur, leading to a frozen program. Discover common causes of deadlock, including issues with mutex locks, inter-thread dependencies, and improper resource management.
Thread Deadlock in Python
A deadlock is a concurrency failure mode where one or more threads wait for a condition that never occurs. This situation causes the threads to be unable to progress, making the program stuck or frozen.
Common Causes of Thread Deadlocks
- A thread attempts to acquire the same mutex lock twice.
- Threads wait on each other (e.g., A waits on B, B waits on A).
- A thread fails to release a resource such as a lock, semaphore, condition, or event.
- Threads acquire mutex locks in different orders (fail to perform lock ordering).
To prevent data inconsistency, concurrent handling should be synchronized so that resources are locked when one thread is using them.
The Lock Object
The Lock
class in the threading
module allows you to synchronize threads. A new lock is created by calling the Lock()
method, which returns the new lock object.
The acquire()
Method
This method changes the state to locked and returns immediately if the state is unlocked. It takes an optional blocking argument.
Syntax
Lock.acquire(blocking, timeout)
The release()
Method
This method changes the state to unlocked when called in the locked state. It can be called from any thread, not just the one that acquired the lock.
Syntax
Lock.release()
Example
In the following program, two threads try to call the synchronized()
method. One thread acquires the lock and gains access, while the other waits. Once the first thread completes, the lock is released and available for the second thread.
Example
from threading import Thread, Lock
import time
lock = Lock()
threads = []
class myThread(Thread):
def __init__(self, name):
Thread.__init__(self)
self.name = name
def run(self):
lock.acquire()
synchronized(self.name)
lock.release()
def synchronized(threadName):
print(f"{threadName} has acquired lock and is running synchronized method")
counter = 5
while counter:
print('**', end='')
time.sleep(2)
counter -= 1
print('\nlock released for', threadName)
t1 = myThread('Thread1')
t2 = myThread('Thread2')
t1.start()
threads.append(t1)
t2.start()
threads.append(t2)
for t in threads:
t.join()
print("end of main thread")
Output
Thread1 has acquired lock and is running synchronized method
**********
lock released for Thread1
Thread2 has acquired lock and is running synchronized method
**********
lock released for Thread2
end of main thread
The Semaphore Object
Python supports thread synchronization with a semaphore. A semaphore uses an internal counter, which is decremented by each acquire()
call and incremented by each release()
call. The counter can never go below zero. When it is zero, the acquire()
method blocks until a release()
method is called.
The acquire()
Method
If the internal counter is larger than zero, it is decremented by one and returns True immediately. If zero, it blocks until a release()
call increments the counter above zero.
The release()
Method
This method releases a semaphore, incrementing the internal counter by one. If other threads are waiting for the semaphore to become larger than zero, they are awoken.
Example
Example
from threading import Semaphore, Thread
import time
# creating thread instance where count = 3
lock = Semaphore(4)
def synchronized(name):
lock.acquire()
for n in range(3):
print('Hello! ', end='')
time.sleep(1)
print(name)
lock.release()
# creating multiple threads
thread_1 = Thread(target=synchronized, args=('Thread 1',))
thread_2 = Thread(target=synchronized, args=('Thread 2',))
thread_3 = Thread(target=synchronized, args=('Thread 3',))
# starting the threads
thread_1.start()
thread_2.start()
thread_3.start()
Output
Hello! Hello! Hello! Thread 1
Hello! Thread 2
Thread 3
Hello! Hello! Thread 1
Hello! Thread 3
Thread 2
Hello! Hello! Thread 1
Thread 3
Thread 2