Synchronization Concepts

Introduction

When multiple threads access and modify shared resources simultaneously, unexpected results can occur. Synchronization is the process of controlling thread access to shared resources to ensure data consistency and prevent conflicts.

Synchronization is essential in:

  • Multithreaded Applications

  • Selenium Automation Frameworks

  • API Automation Frameworks

  • Banking Systems

  • Inventory Management Systems

  • Logging Systems

  • Database Operations

In this tutorial, you will learn synchronization concepts, race conditions, locks, semaphores, events, practical examples, automation testing use cases, common mistakes, and best practices.


What is Synchronization?

Synchronization ensures that only the appropriate thread accesses a shared resource at a given time.

Example

Imagine two people updating the same bank account balance simultaneously.

Without synchronization:

  • Person A reads balance = 1000

  • Person B reads balance = 1000

  • Person A adds 500 → 1500

  • Person B adds 300 → 1300

Final balance becomes 1300 instead of 1800.

This problem occurs because multiple threads accessed the shared resource simultaneously.


What is a Race Condition?

A race condition occurs when multiple threads access and modify shared data at the same time, causing unpredictable results.

Example

import threading

counter = 0

def increment():

    global counter

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

thread1 = threading.Thread(
    target=increment
)

thread2 = threading.Thread(
    target=increment
)

thread1.start()
thread2.start()

thread1.join()
thread2.join()

print(counter)

Expected Output

200000

Possible Output

183524

The result may vary because of a race condition.


Synchronization Tools in Python

Python provides synchronization primitives through the threading module.

Tool Purpose
Lock Mutual exclusion
RLock Reentrant lock
Semaphore Limit access count
Event Thread signaling
Condition Wait/notify mechanism
Barrier Synchronize thread completion

Lock

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

Creating a Lock

import threading

lock = threading.Lock()

Using Lock

Example

import threading

counter = 0

lock = threading.Lock()

def increment():

    global counter

    for _ in range(100000):

        with lock:
            counter += 1

thread1 = threading.Thread(
    target=increment
)

thread2 = threading.Thread(
    target=increment
)

thread1.start()
thread2.start()

thread1.join()
thread2.join()

print(counter)

Output

200000

Now the result is consistent.


Manual Lock Acquisition

Example

lock.acquire()

try:
    # critical section
    pass

finally:
    lock.release()

Using with Statement

Preferred approach:

with lock:
    # critical section
    pass

Automatically releases the lock.


What is a Critical Section?

A critical section is the part of code where shared resources are accessed.

Example:

counter += 1

Only one thread should execute this section at a time.


RLock (Reentrant Lock)

A thread can acquire an RLock multiple times.

Example

import threading

lock = threading.RLock()

def task():

    with lock:

        print("Outer Lock")

        with lock:

            print("Inner Lock")

task()

Output

Outer Lock
Inner Lock

Semaphore

A semaphore allows a fixed number of threads to access a resource simultaneously.

Example

import threading
import time

semaphore = threading.Semaphore(2)

def worker(number):

    with semaphore:

        print(
            f"Thread {number} Running"
        )

        time.sleep(2)

for i in range(5):

    thread = threading.Thread(
        target=worker,
        args=(i,)
    )

    thread.start()

Behavior

Only 2 threads run at a time.


Event

An Event allows one thread to signal another thread.

Example

import threading

event = threading.Event()

def waiter():

    print("Waiting...")

    event.wait()

    print("Started")

thread = threading.Thread(
    target=waiter
)

thread.start()

event.set()

Output

Waiting...
Started

Condition

Used when threads need to wait for a specific condition.

Example

import threading

condition = threading.Condition()

items = []

def consumer():

    with condition:

        while not items:
            condition.wait()

        print(items.pop())

def producer():

    with condition:

        items.append("Data")

        condition.notify()

threading.Thread(
    target=consumer
).start()

threading.Thread(
    target=producer
).start()

Barrier

A Barrier waits until a specified number of threads reach the same point.

Example

import threading

barrier = threading.Barrier(3)

def worker(number):

    print(
        f"Thread {number} Ready"
    )

    barrier.wait()

    print(
        f"Thread {number} Started"
    )

for i in range(3):

    threading.Thread(
        target=worker,
        args=(i,)
    ).start()

Real-World Example: Bank Account

Without Lock

balance = 1000

def withdraw(amount):

    global balance

    balance -= amount

Multiple withdrawals can corrupt data.


With Lock

lock = threading.Lock()

def withdraw(amount):

    global balance

    with lock:

        balance -= amount

Safe execution.


Selenium Automation Example

Shared Report File

Without synchronization:

report.write("Test Passed")

Multiple threads may write simultaneously.


With Lock

lock = threading.Lock()

with lock:

    report.write("Test Passed")

Prevents report corruption.


API Automation Example

Rate-limit concurrent API calls.

semaphore = threading.Semaphore(5)

Only 5 API requests execute simultaneously.


Common Mistakes Beginners Make

Not Protecting Shared Data

Incorrect

counter += 1

Race conditions may occur.


Correct

with lock:
    counter += 1

Forgetting Lock Release

Incorrect

lock.acquire()

# exception occurs

# lock.release() never called

Can cause deadlocks.


Correct

with lock:
    pass

Overusing Locks

Too many locks reduce performance.

Lock only critical sections.


Nested Locks Causing Deadlocks

Example

Thread A waits for Lock B.

Thread B waits for Lock A.

Both threads become stuck.


Best Practices

Use with Lock

with lock:
    pass

Keep Critical Sections Small

with lock:
    counter += 1

Avoid long operations inside locks.


Use Semaphore for Resource Pools

Example:

  • Database connections

  • API requests

  • Browser instances


Prefer Higher-Level Constructs

Use:

ThreadPoolExecutor

when possible.


Avoid Deadlocks

Always acquire locks in a consistent order.


Advantages of Synchronization

  • Prevents race conditions

  • Maintains data consistency

  • Ensures thread safety

  • Improves application reliability


Limitations

  • Adds complexity

  • May reduce performance

  • Incorrect usage can cause deadlocks


Synchronization Tools Summary

Tool Purpose
Lock One thread at a time
RLock Same thread can re-acquire
Semaphore Limited concurrent access
Event Thread signaling
Condition Wait/notify
Barrier Wait for all threads

Conclusion

Synchronization is one of the most important concepts in multithreaded programming. Without synchronization, shared data can become corrupted due to race conditions and concurrent access issues.

Python provides powerful synchronization primitives such as Lock, RLock, Semaphore, Event, Condition, and Barrier to coordinate thread execution safely.

Understanding these concepts is essential when building reliable automation frameworks, web applications, APIs, and enterprise systems.


Frequently Asked Questions (FAQs)

What is synchronization?

Synchronization controls access to shared resources among multiple threads.


What is a race condition?

A situation where multiple threads modify shared data simultaneously, producing unpredictable results.


Which synchronization tool is most commonly used?

threading.Lock()

What is a semaphore?

A synchronization mechanism that limits the number of threads accessing a resource.


What causes deadlocks?

Deadlocks occur when threads wait indefinitely for resources held by each other.


Key Takeaways

  • Synchronization prevents conflicts between threads.

  • Race conditions occur when shared data is modified concurrently.

  • Lock provides mutual exclusion.

  • RLock allows reentrant locking.

  • Semaphore limits concurrent access.

  • Event enables thread signaling.

  • Condition supports wait/notify communication.

  • Barrier synchronizes multiple threads.

  • Use with lock: whenever possible.

  • Proper synchronization is critical for thread-safe applications.