Header Ads Widget

⚡ Premium Tools Hub • EXE Apps + Full Python Source Code
Lite • Pro • Bundle Packs • Instant Download

Python Thread Synchronizing Tutorial – Complete Guide with Examples

Python - Synchronizing Threads

When multiple threads access shared data at the same time, problems can occur such as incorrect results or data corruption. This is known as a race condition.

To solve this problem, Python provides thread synchronization techniques.

Thread synchronization ensures that only one thread accesses shared resources at a time, or that threads coordinate properly with each other.

In this tutorial, you will learn what thread synchronization is, why it is needed, and how to implement it in Python.


What is Thread Synchronization?

Thread synchronization is:

A mechanism that controls the access of multiple threads to shared resources.

It ensures:

  • Data consistency
  • Safe execution
  • Avoidance of race conditions

Why Do We Need Synchronization?

Without synchronization:

  • Multiple threads modify data at the same time
  • Results become unpredictable
  • Data may get corrupted

Example of Problem Without Synchronization

import threading

counter = 0

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

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

t1.start()
t2.start()

t1.join()
t2.join()

print("Final Counter:", counter)

Expected vs Actual Output

Expected:

200000

Actual (may vary):

Less than 200000

Why This Happens

Because:

  • Both threads access counter at the same time
  • Operations overlap
  • Data is overwritten

This is called a race condition.


Solution: Thread Synchronization

Python provides several ways to synchronize threads.


Method 1: Using Lock

A Lock allows only one thread to access a resource at a time.


Syntax

lock = threading.Lock()

Example with Lock

import threading

counter = 0
lock = threading.Lock()

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

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

t1.start()
t2.start()

t1.join()
t2.join()

print("Final Counter:", counter)

Better Way: Using with Statement

import threading

counter = 0
lock = threading.Lock()

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

Method 2: RLock (Reentrant Lock)

An RLock allows a thread to acquire the same lock multiple times.

lock = threading.RLock()

Example

import threading

lock = threading.RLock()

def task():
    with lock:
        print("Outer lock")
        with lock:
            print("Inner lock")

t = threading.Thread(target=task)
t.start()

Method 3: Semaphore

A Semaphore allows multiple threads to access a resource with a limit.


Example

import threading

semaphore = threading.Semaphore(2)

def task(name):
    with semaphore:
        print(name, "is running")

threads = []

for i in range(5):
    t = threading.Thread(target=task, args=(f"Thread-{i}",))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

Method 4: Event

An Event is used to signal between threads.


Example

import threading
import time

event = threading.Event()

def wait_for_event():
    print("Waiting for event...")
    event.wait()
    print("Event received!")

def trigger_event():
    time.sleep(2)
    event.set()

t1 = threading.Thread(target=wait_for_event)
t2 = threading.Thread(target=trigger_event)

t1.start()
t2.start()

Method 5: Condition

Condition is used for complex thread communication.


Example

import threading

condition = threading.Condition()
data_ready = False

def consumer():
    with condition:
        while not data_ready:
            condition.wait()
        print("Consumed data")

def producer():
    global data_ready
    with condition:
        data_ready = True
        condition.notify()

t1 = threading.Thread(target=consumer)
t2 = threading.Thread(target=producer)

t1.start()
t2.start()

Types of Synchronization

1. Mutual Exclusion

Only one thread accesses resource at a time (Lock).


2. Thread Coordination

Threads communicate and wait (Event, Condition).


3. Resource Limiting

Control number of threads (Semaphore).


Real-World Example

Bank Account System

import threading

balance = 1000
lock = threading.Lock()

def withdraw(amount):
    global balance
    with lock:
        if balance >= amount:
            balance -= amount
            print("Withdraw:", amount)
        else:
            print("Insufficient funds")

t1 = threading.Thread(target=withdraw, args=(700,))
t2 = threading.Thread(target=withdraw, args=(700,))

t1.start()
t2.start()

t1.join()
t2.join()

print("Final Balance:", balance)

Advantages of Synchronization

  • Prevents data corruption
  • Ensures consistency
  • Controls thread execution
  • Improves reliability

Disadvantages

  • Slower performance
  • Can cause deadlocks
  • Increases complexity

Common Mistakes

1. Forgetting to release lock

lock.acquire()
# missing release

2. Overusing locks

Reduces performance.


3. Deadlocks

Threads waiting forever.


Best Practices

1. Use with lock

with lock:
    # safe code

2. Keep critical section small


3. Avoid nested locks when possible


4. Always test concurrency scenarios


Summary

Thread synchronization in Python ensures safe access to shared resources when multiple threads run simultaneously. It prevents race conditions and maintains data consistency using tools like Lock, RLock, Semaphore, Event, and Condition.

Key Takeaways

  • Synchronization prevents race conditions
  • Use Lock for mutual exclusion
  • Use Semaphore for limiting access
  • Use Event/Condition for coordination
  • Essential for safe multithreading

Mastering thread synchronization is critical for building reliable and safe concurrent Python applications.




Post a Comment

0 Comments