Python Multithreading
Estimated reading: 4 minutes 273 views

Python Thread Synchronization & Deadlocks – Secure Your Shared Resources

Introduction – Why Synchronization Matters

Multithreading allows Python to run multiple tasks concurrently. However, when threads access shared resources, it can lead to issues like:

  • Race conditions
  • Inconsistent data
  • 🧨 Deadlocks

That’s why synchronization is essential—to coordinate access between threads and ensure safe, predictable behavior.

In this guide, you’ll learn:

  • What thread synchronization is and why it’s needed
  • How to use locks, semaphores, and events in Python
  • What causes deadlocks and how to avoid them
  • Real-world examples of safe and unsafe thread behavior

What Is Thread Synchronization?

Thread synchronization ensures that only one thread accesses a shared resource at a time, preventing data corruption or unexpected results.


Basic Locking with threading.Lock

import threading

counter = 0
lock = threading.Lock()

def increment():
    global counter
    for _ in range(100000):
        with lock:
            counter += 1

t1 = threading.Thread(target=increment)
t2 = threading.Thread(target=increment)

t1.start()
t2.start()
t1.join()
t2.join()

print("Counter:", counter)

Output (safe):

Counter: 200000

Without lock → Output could be inconsistent due to race conditions.


Manual Lock / Unlock (Avoid if possible)

lock.acquire()
try:
    # critical section
finally:
    lock.release()

Use with lock: for automatic handling (context manager).


Synchronization with threading.RLock

RLock (Reentrant Lock) allows a thread to acquire the same lock multiple times.

rlock = threading.RLock()

def nested():
    with rlock:
        print("Outer lock acquired")
        with rlock:
            print("Inner lock acquired")

Useful when the same thread re-enters a locked block.


🎚️ Using threading.Semaphore

Semaphores allow limited access to a resource.

sem = threading.Semaphore(2)  # Only 2 threads allowed simultaneously

def access():
    with sem:
        print(threading.current_thread().name, "accessing")
        time.sleep(1)

for i in range(4):
    threading.Thread(target=access).start()

Controls concurrent thread count—great for connection pools, file handles, etc.


🔔 threading.Event() for Signaling Between Threads

event = threading.Event()

def worker():
    print("Waiting for signal...")
    event.wait()
    print("Signal received!")

threading.Thread(target=worker).start()
time.sleep(2)
event.set()  # Send signal

Ideal for thread coordination, e.g., starting task after a flag.


What Is a Deadlock?

A deadlock occurs when two or more threads are waiting on each other’s resources and can’t proceed.


Deadlock Example

lock1 = threading.Lock()
lock2 = threading.Lock()

def task1():
    with lock1:
        print("Task1 acquired lock1")
        time.sleep(1)
        with lock2:
            print("Task1 acquired lock2")

def task2():
    with lock2:
        print("Task2 acquired lock2")
        time.sleep(1)
        with lock1:
            print("Task2 acquired lock1")

t1 = threading.Thread(target=task1)
t2 = threading.Thread(target=task2)

t1.start()
t2.start()
t1.join()
t2.join()

Output:

Task1 acquired lock1  
Task2 acquired lock2  
(hangs due to circular wait)

How to Prevent Deadlocks

StrategyDescription
Always acquire locks in orderDecide lock acquisition sequence across threads
Use RLock where reentrancy is neededPrevents blocking on re-entry
Use timeout in acquire()Avoid indefinite wait
Detect circular waitDesign threads with acyclic lock dependency

Using acquire(timeout=x) to Avoid Deadlock

if lock1.acquire(timeout=2):
    if lock2.acquire(timeout=2):
        # do work
        lock2.release()
    lock1.release()
else:
    print("Could not acquire locks safely")

Real-World Use Case – File Writer Synchronization

file_lock = threading.Lock()

def write_to_file():
    with file_lock:
        with open("data.txt", "a") as f:
            f.write("Logged entry\n")

Prevents multiple threads from writing to a file simultaneously.


Best Practices

Do This Avoid This
Use with lock: for safe accessForgetting to release manually
Acquire locks in consistent orderNested, unordered locks cause deadlocks
Use RLock when recursive locking is neededRe-locking regular locks from same thread
Use semaphores for pooled resourcesLetting threads overwhelm the system
Use timeouts and events to detect issuesWaiting forever without fallback

Summary – Recap & Next Steps

Thread synchronization ensures safe access to shared resources in multithreaded programs. Misusing locks can cause race conditions or deadlocks, so understanding these mechanisms is essential.

Key Takeaways:

  • Use Lock, RLock, and Semaphore to manage concurrent access
  • Avoid deadlocks by acquiring locks in a consistent order
  • Use Event() for signaling between threads
  • Always protect shared data using proper synchronization tools

Real-World Relevance:
Used in file systems, databases, network services, and task schedulers.


FAQ – Python Synchronization & Deadlocks

When should I use a Lock in Python?

Use it to protect shared data accessed by multiple threads simultaneously.

What is a race condition?

A bug caused by unsynchronized access to shared resources, resulting in inconsistent data.

How do I avoid deadlocks?

Acquire locks in a consistent order, use timeouts, and avoid nested locking when possible.

What is the difference between Lock and RLock?

  • Lock: Cannot be acquired multiple times by the same thread
  • RLock: Can be safely re-acquired by the same thread (reentrant)

Are Python Locks reentrant?

Lock is not reentrant. Use RLock for nested lock scenarios.


Share Now :
Share

Python Thread Synchronization / Deadlocks

Or Copy Link

CONTENTS
Scroll to Top