Python Multithreading
Estimated reading: 4 minutes 26 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 :

Leave a Reply

Your email address will not be published. Required fields are marked *

Share

Python Thread Synchronization / Deadlocks

Or Copy Link

CONTENTS
Scroll to Top