0
0
PythonDebug / FixIntermediate · 4 min read

How to Avoid Deadlock in Python: Causes and Fixes

Deadlocks in Python happen when two or more threads wait forever for each other to release Lock objects. To avoid deadlocks, always acquire locks in a consistent order and use threading.Lock with timeout or higher-level synchronization tools like threading.RLock or threading.Condition.
🔍

Why This Happens

A deadlock occurs when two threads each hold a lock the other needs, so both wait forever. This happens because locks are not released before trying to get another lock, causing a cycle of waiting.

python
import threading

lock_a = threading.Lock()
lock_b = threading.Lock()

def thread1():
    lock_a.acquire()
    print("Thread 1 acquired lock A")
    lock_b.acquire()
    print("Thread 1 acquired lock B")
    lock_b.release()
    lock_a.release()

def thread2():
    lock_b.acquire()
    print("Thread 2 acquired lock B")
    lock_a.acquire()
    print("Thread 2 acquired lock A")
    lock_a.release()
    lock_b.release()

t1 = threading.Thread(target=thread1)
t2 = threading.Thread(target=thread2)

t1.start()
t2.start()

t1.join()
t2.join()
🔧

The Fix

To fix deadlocks, always acquire locks in the same order in every thread. This prevents circular waiting. Alternatively, use with statements for automatic release and consider using threading.RLock which allows the same thread to acquire the lock multiple times safely.

python
import threading

lock_a = threading.Lock()
lock_b = threading.Lock()

def thread1():
    with lock_a:
        print("Thread 1 acquired lock A")
        with lock_b:
            print("Thread 1 acquired lock B")

def thread2():
    with lock_a:
        print("Thread 2 acquired lock A")
        with lock_b:
            print("Thread 2 acquired lock B")


t1 = threading.Thread(target=thread1)
t2 = threading.Thread(target=thread2)

t1.start()
t2.start()

t1.join()
t2.join()
Output
Thread 1 acquired lock A Thread 1 acquired lock B Thread 2 acquired lock A Thread 2 acquired lock B
🛡️

Prevention

To avoid deadlocks in the future, follow these best practices:

  • Always acquire multiple locks in a consistent global order.
  • Use with blocks to ensure locks are released automatically.
  • Consider using higher-level synchronization like threading.RLock or threading.Condition.
  • Use timeouts with lock.acquire(timeout=) to avoid waiting forever.
  • Keep critical sections short to reduce lock hold time.
⚠️

Related Errors

Other common threading issues include:

  • Race conditions: When threads access shared data without proper locking, causing inconsistent results.
  • Starvation: When a thread never gets CPU time because others hold locks too long.
  • Resource leaks: When locks are not released due to exceptions or logic errors.

Using with statements and proper lock ordering helps prevent these problems.

Key Takeaways

Always acquire multiple locks in the same order to prevent deadlocks.
Use with statements to automatically release locks and avoid forgetting to unlock.
Consider higher-level synchronization tools like RLock or Condition for complex cases.
Use lock acquire timeouts to avoid waiting forever.
Keep critical sections short to reduce lock contention.