Python

P
PythonHub
Progress 0%

Python

  • Home
  • History of Python
  • Applications of Python
  • Introduction To Python
    • What is Python
    • Character Set
    • Tokens in Python
    • Python Execution Mode
    • Variable And Identifiers
    • Data Types in Python
    • Operators And Expressions
    • Constants in Python
    • Assignment Statement
    • Input / Output in Python
    • Simple Python Scripts
    • Namespace in Python
    • šŸ“ Assignments
  • Operators in Python
    • Arithmetic Operators
    • Assignment Operators
    • Shorthand Assignment Operators
    • Relational Operators
    • Logical Operators
    • Bitwise Operators
    • Special Operators
    • šŸ“ Assignments
  • Input Output in Python
    • Accept Input
    • Output Formatting
    • šŸ“ Assignments
  • Conditional Statement
    • Decision Making
    • if Statement
    • IF-ELSE STATEMENT
    • IF-ELSE LADDER
    • NESTED IF-ELSE
    • Short Hand IF-ELSE
    • šŸ“ Assignments
  • Loops
    • Introduction to Loops
    • While Loop
    • Nested While Loop
    • šŸ“ While Loop Assignments
    • For Loop
    • For Loop Examples
    • Nested For Loop
    • Nested For Loop Examples
    • Infinite While Loops
    • Infinite For Loops
    • Break, Continue and Else in Loops
    • Difference Between For and While Loop
    • For Each Loop
    • šŸ“ For Each Assignments
    • šŸ“ All Loops Assignments
  • List
    • List in Python
    • Access List Elements
    • List Functions
    • Iterate (Loop) List
    • List Comprehension
    • šŸ“ Assignments
  • Tuple
    • Tuple in Python
    • Access Tuple Elements
    • Tuple Functions
    • Iterate (Loop) Tuple
    • Unpack Tuple
    • Tuple Comprehension
    • šŸ“ Assignments
  • Set
    • Set in Python
    • Access Set Elements
    • Set Methods
    • Iterate (Loop) Set
    • Pack/Unpack Set
    • Set Comprehension
    • šŸ“ Assignments
  • Dictionary
    • Dictionary
    • Access Dictionary Items
    • Dictionary Methods
    • Iterate (Loop) Dictionary
    • Formatting Dictionaries
    • Nested Dictionaries
    • Dictionary Comprehension
    • šŸ“ Assignments
  • Diff List Tuple Set Dictionary
    • List vs Tuple
    • List vs Set
    • List vs Dictionary
    • Tuple vs Set
    • Tuple vs Dictionary
    • Dictionary vs Set
    • šŸ“ Assignments
  • Exception
    • Error vs Exception
    • Types of Exception
    • Exception Handling
    • User Defined Exception
    • Logging Exception
    • šŸ“ Assignments
  • Functions
    • Introduction to Functions
    • Modular Programming
    • Types of Functions
    • Inbuilt Functions
    • Need For User-Defined Function
    • Elements of User Defined Function
    • Function Arguments
    • Nesting of Functions
    • Recursion
    • Global Local and Non Local
    • Python Lambda Functions
    • šŸ“ Assignments
  • Python Module
    • Introduction to Module
    • Inbuilt Modules in Python
    • User Defined Module
    • šŸ“ Assignments
  • File Handling
    • Introduction to Files
    • Create File
    • Read Files
    • Write to File
    • Rename File
    • Copy File
    • Move File
    • List Files in Directory
    • Binary Files
    • Zipping and Unzipping Files
    • šŸ“ Assignments
  • Strings
    • Basics of Strings
    • String Special Operators
    • String Formatting Operators
    • String Methods
    • šŸ“ Assignments
  • Regular Expressions
  • Python OOPS
    • Basics of Object Oriented
    • What are Classes and Objects?
    • Creating Class and Object
    • OOP vs Procedural Programming
    • Difference Between Classes and Objects
    • Constructors
    • Destructor
    • Built Class Methods and Attributes
    • Class and Instance Variables
    • Inheritance in Python
    • Single Inheritance
    • Multiple Inheritance
    • Multilevel Inheritance
    • Hierarchical Inheritance
    • Hybrid Inheritance
    • Abstraction
    • Method Overriding
    • Abstract Method
    • Interfaces in Python
    • Abstract Class vs Interface
    • Public, Private and Protected
    • Overloading vs Overriding
    • Inheritance vs Composition
    • Encapsulation
    • Polymorphism
    • Inner Classes
    • šŸ“ Assignments
  • Advanced Python
    • Iterator in Python
    • Generator in Python
    • Decorator in Python
    • Type Hints in Python
    • Async/Await Programming
    • Dataclasses in Python
    • Context Managers in Python
    • Property Decorator in Python
    • __slots__ in Python
    • Enums in Python
    • Walrus Operator in Python
    • Match-Case in Python
    • šŸ“ Assignments
  • Python Standard Library
    • Collections Module
    • Itertools Module
    • Functools Module
    • Datetime Module
    • JSON Module
    • OS Module
    • Sys Module
    • Random Module
    • Math Module
    • šŸ“ Assignments
  • Python Testing
    • Unit Testing in Python
    • Pytest Framework
    • Mocking in Python
    • šŸ“ Assignments
  • Python Best Practices
    • PEP 8 Style Guide
    • Docstrings in Python
    • Logging in Python
    • Code Optimization Tips
    • Debugging Techniques
    • šŸ“ Assignments
  • MySQL Database in Python
    • Introduction to MySQL with Python
    • DBMS vs File System
    • Connecting to MySQL Database
    • Create Database in MySQL
    • Create Table in MySQL
    • Insert Data in MySQL
    • Insert Multiple Rows
    • Select Data from MySQL
    • WHERE Clause in MySQL
    • Update Data in MySQL
    • Delete Data from MySQL
    • Parameterized Queries
    • Transaction Management
    • Error Handling
    • Connection Pooling
    • MySQL Drivers Guide
    • Joins in MySQL
    • Aggregation Functions
    • Backup MySQL Database
    • Best Practices
    • šŸ“ Assignments
  • MySQL Database Operations
    • SELECT Statement
    • MySQL Operators
    • DDL Statements
    • DML Statements
    • Subqueries
    • JOIN Operations
    • Aggregation
    • Case Study
    • šŸ“ Assignments
  • Graphics in Python
  • Threads in Python
    • Introduction to Threads
    • Process vs Threads
    • Concurrent Programming & GIL
    • Uses of Threads
    • Creating Threads
    • Single Tasking
    • Multi Tasking
    • Thread Synchronization
    • šŸ“ Assignments
  • Interview Questions & Answers
  • Python Case Studies
  • Multiple Choice Questions
  • šŸ“ Practice Problems
Get in Touch
  • tech2dsm@gmail.com

© Sankalan Data Tech

Python Language Interactive Tutorial

Python: Thread Synchronization

Python Thread Synchronization - Complete Guide

Learn how to synchronize threads for safe concurrent programming.

Created by Sankalan Data Tech Team Verified
Data Engineers, Analysts, Scientists & Trainers
Created by experienced Python developers, data engineers, and data scientists to make programming easy through practical examples, real-world experience, and clear explanations.
Read Time: 12 minutes Level: Intermediate Topic: Python Concurrency Focus: Thread Safety & Synchronization
On this page:
  • What is Thread Synchronization?
  • Race Conditions
  • Locks (Mutex)
  • Reentrant Locks
  • Semaphores
  • Condition Variables
  • Events
  • Barriers
  • Synchronization Comparison
  • Best Practices
  • Try It Yourself
  • Quiz
  • FAQ
Share this tutorial:
Twitter LinkedIn Facebook WhatsApp Reddit Telegram Email Copy Link
What You'll Learn Here
  • What is thread synchronization — preventing race conditions
  • Locks (Mutex) — the most basic synchronization tool
  • Reentrant Locks — locks that can be reacquired
  • Semaphores — controlling access to limited resources
  • Condition Variables — waiting for specific conditions
  • Events and Barriers — thread coordination

What is Thread Synchronization?

Thread synchronization is the process of coordinating thread execution to prevent conflicts when multiple threads access shared resources. Without synchronization, threads can interfere with each other, causing data corruption and unpredictable behavior.

🚦 Think of it like a traffic intersection.

Without traffic lights, cars crash into each other. With traffic lights, cars take turns and pass safely. Thread synchronization is like traffic lights for your code — it ensures threads take turns accessing shared resources.

šŸ”’ Locks

Exclusive access to resources

šŸ” Reentrant Locks

Lock that can be reacquired

šŸ“Š Semaphores

Controlled concurrent access

šŸ“¢ Events & Barriers

Thread coordination

šŸ’” Key concept: Synchronization prevents race conditions and ensures data consistency in multi-threaded programs.

Race Conditions

1

What is a Race Condition?

A race condition occurs when multiple threads access shared data concurrently and at least one thread modifies it, leading to unexpected results due to timing interleaving.

# ============================================================
# RACE CONDITION EXAMPLE
# ============================================================

import threading
import time

# Shared counter
counter = 0

def increment_counter():
    """Increment counter (NOT thread-safe)"""
    global counter
    temp = counter          # Read
    time.sleep(0.000001)    # Simulate context switch
    counter = temp + 1      # Write

print("=" * 40)
print("RACE CONDITION DEMONSTRATION")
print("=" * 40)

print(f"Initial counter: {counter}")

# Create threads
threads = []
for _ in range(100):
    t = threading.Thread(target=increment_counter)
    threads.append(t)

# Start threads
for t in threads:
    t.start()

# Wait for threads
for t in threads:
    t.join()

print(f"Final counter: {counter}")
print(f"Expected: 100")

print("""
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ OBSERVATIONS:                                                 │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ • Counter should be 100 but may be less                      │
│ • Threads interfered with each other                         │
│ • This is a classic race condition                          │
│ • Need synchronization to fix it                            │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
""")

Race condition key points:

  • Multiple threads — access shared data
  • Interleaving — unpredictable timing
  • Corruption — data inconsistency
  • Prevention — use synchronization

Quick Check: What is a race condition? (Answer: When threads interfere with each other, causing data corruption)

Locks (Mutex)

2

Exclusive Access with Locks

A lock (mutex) ensures that only one thread can access a shared resource at a time. Threads acquire the lock before accessing the resource and release it afterward.

# ============================================================
# USING LOCKS FOR SYNCHRONIZATION
# ============================================================

import threading
import time

# Shared counter
counter = 0
lock = threading.Lock()

def increment_counter():
    """Increment counter WITH lock (thread-safe)"""
    global counter
    lock.acquire()      # Acquire lock
    try:
        temp = counter
        time.sleep(0.000001)
        counter = temp + 1
    finally:
        lock.release()  # Always release lock

print("=" * 40)
print("THREAD SAFE WITH LOCK")
print("=" * 40)

print(f"Initial counter: {counter}")

# Create threads
threads = []
for _ in range(100):
    t = threading.Thread(target=increment_counter)
    threads.append(t)

# Start threads
for t in threads:
    t.start()

# Wait for threads
for t in threads:
    t.join()

print(f"Final counter: {counter}")
print(f"Expected: 100 āœ“")

# ============================================================
# LOCK WITH CONTEXT MANAGER
# ============================================================

print("\n" + "=" * 40)
print("LOCK WITH CONTEXT MANAGER")
print("=" * 40)

counter2 = 0
lock2 = threading.Lock()

def increment_with_context():
    global counter2
    with lock2:          # Context manager automatically releases
        temp = counter2
        time.sleep(0.000001)
        counter2 = temp + 1

threads = []
for _ in range(100):
    t = threading.Thread(target=increment_with_context)
    threads.append(t)

for t in threads:
    t.start()

for t in threads:
    t.join()

print(f"Final counter (with context): {counter2}")

print("""
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ LOCK (MUTEX) ADVANTAGES:                                      │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ • Simple to use                                                │
│ • Guarantees exclusive access                                  │
│ • Prevents race conditions                                    │
│ • Works with context manager (`with lock`)                    │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ LOCK DISADVANTAGES:                                           │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ • Can cause deadlocks if not careful                          │
│ • Performance overhead                                        │
│ • Not reentrant (can't be reacquired)                        │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
""")

Lock key points:

  • Exclusive — only one thread at a time
  • Simple — easy to implement
  • Context manager — use `with lock`
  • Deadlock risk — must release properly

Quick Check: What does a lock (mutex) do? (Answer: Ensures exclusive access to a shared resource)

Reentrant Locks

3

Locks That Can Be Reacquired

A reentrant lock (RLock) allows a thread to acquire the same lock multiple times without deadlocking. This is useful when a function that holds a lock calls another function that also needs the lock.

# ============================================================
# REENTRANT LOCKS
# ============================================================

import threading
import time

# Regular lock vs reentrant lock
regular_lock = threading.Lock()
reentrant_lock = threading.RLock()

print("=" * 40)
print("REENTRANT LOCK EXAMPLE")
print("=" * 40)

def function_a():
    """Function that acquires a lock"""
    print("  function_a: acquiring lock...")
    reentrant_lock.acquire()
    print("  function_a: lock acquired")
    function_b()  # Calls function_b
    print("  function_a: releasing lock...")
    reentrant_lock.release()

def function_b():
    """Function that also needs the same lock"""
    print("    function_b: acquiring lock...")
    reentrant_lock.acquire()
    print("    function_b: lock acquired")
    print("    function_b: doing work...")
    time.sleep(0.1)
    print("    function_b: releasing lock...")
    reentrant_lock.release()

# This works with RLock
print("\nUsing RLock (reentrant):")
t = threading.Thread(target=function_a)
t.start()
t.join()

print("\nāœ“ RLock works because it tracks the owning thread")

# ============================================================
# RLock vs Lock Comparison
# ============================================================

print("\n" + "=" * 40)
print("RLock vs Lock")
print("=" * 40)

# RLock can be reacquired
lock_r = threading.RLock()
with lock_r:
    print("  RLock: first acquire")
    with lock_r:  # This works!
        print("  RLock: second acquire (reentrant)")

# Lock cannot be reacquired
lock_l = threading.Lock()
with lock_l:
    print("  Lock: first acquire")
    try:
        with lock_l:  # This would deadlock
            print("  This won't execute")
    except Exception as e:
        print(f"  Lock: {type(e).__name__} - cannot reacquire")

print("""
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ RLock ADVANTAGES:                                             │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ • Can be reacquired by the same thread                       │
│ • Prevents deadlocks in nested calls                        │
│ • Tracks acquisition count                                   │
│ • Same interface as Lock                                     │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ RLock DISADVANTAGES:                                         │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ • Slightly more overhead                                     │
│ • Can hide design problems                                   │
│ • Only use when needed                                       │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
""")

Reentrant lock key points:

  • Reentrant — same thread can acquire multiple times
  • Tracks owner — knows which thread holds it
  • Counts acquires — must release same number
  • Nested calls — prevents deadlocks

Quick Check: When should you use RLock instead of Lock? (Answer: When a function that holds a lock calls another function that also needs the lock)

Semaphores

4

Controlling Access to Limited Resources

A semaphore limits the number of threads that can access a resource simultaneously. It's like a permit system — threads acquire permits before using the resource and release them when done.

# ============================================================
# SEMAPHORE EXAMPLE
# ============================================================

import threading
import time
import random

# Semaphore with capacity 3 (max 3 concurrent threads)
semaphore = threading.Semaphore(3)

def use_resource(thread_id):
    """Use a limited resource with semaphore control"""
    print(f"Thread {thread_id}: Waiting for permit...")
    semaphore.acquire()
    try:
        print(f"Thread {thread_id}: Got permit! Using resource...")
        time.sleep(random.uniform(0.5, 1.5))
        print(f"Thread {thread_id}: Done using resource.")
    finally:
        semaphore.release()
        print(f"Thread {thread_id}: Released permit.")

print("=" * 40)
print("SEMAPHORE DEMONSTRATION")
print("=" * 40)

print("Semaphore capacity: 3 (max 3 concurrent threads)")
print()

# Create 8 threads
threads = []
for i in range(8):
    t = threading.Thread(target=use_resource, args=(i,))
    threads.append(t)

# Start all threads
for t in threads:
    t.start()

# Wait for all threads
for t in threads:
    t.join()

print("\nAll threads completed!")

# ============================================================
# BOUNDED SEMAPHORE
# ============================================================

print("\n" + "=" * 40)
print("BOUNDED SEMAPHORE")
print("=" * 40)

# BoundedSemaphore prevents releasing more than acquired
bounded = threading.BoundedSemaphore(2)

print("BoundedSemaphore capacity: 2")
print("Acquiring lock...")
bounded.acquire()
print("Lock acquired (1/2)")
bounded.acquire()
print("Lock acquired (2/2)")

print("\nReleasing lock...")
bounded.release()
print("Lock released (1/2)")
bounded.release()
print("Lock released (2/2)")

try:
    print("\nAttempting extra release...")
    bounded.release()  # This raises ValueError
except ValueError as e:
    print(f"ValueError: {e}")

print("""
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ SEMAPHORE ADVANTAGES:                                         │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ • Limits concurrent resource usage                            │
│ • Configurable capacity                                       │
│ • Bounded version prevents errors                            │
│ • Great for connection pools                                 │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ SEMAPHORE DISADVANTAGES:                                     │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ • More complex than locks                                    │
│ • Must manage permits carefully                              │
│ • BoundedSemaphore needed for safety                        │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
""")

Semaphore key points:

  • Limits concurrency — controls how many threads
  • Permit system — acquire/release permits
  • BoundedSemaphore — prevents over-release
  • Resource pools — database connections, etc.

Quick Check: What is a semaphore used for? (Answer: Limiting the number of threads accessing a resource)

Condition Variables

5

Waiting for Specific Conditions

A condition variable allows threads to wait until a specific condition is met. It combines a lock with wait/notify functionality, making it perfect for producer-consumer scenarios.

# ============================================================
# CONDITION VARIABLE - PRODUCER CONSUMER
# ============================================================

import threading
import time
import random

class BoundedBuffer:
    """Producer-Consumer with Condition Variable"""
    
    def __init__(self, capacity=5):
        self.buffer = []
        self.capacity = capacity
        self.condition = threading.Condition()
    
    def produce(self, item):
        """Add item to buffer (producer)"""
        with self.condition:
            while len(self.buffer) >= self.capacity:
                print(f"  Buffer full. Producer waiting...")
                self.condition.wait()
            
            self.buffer.append(item)
            print(f"  Produced: {item} (buffer: {len(self.buffer)}/{self.capacity})")
            self.condition.notify()
    
    def consume(self):
        """Remove item from buffer (consumer)"""
        with self.condition:
            while len(self.buffer) == 0:
                print(f"  Buffer empty. Consumer waiting...")
                self.condition.wait()
            
            item = self.buffer.pop(0)
            print(f"  Consumed: {item} (buffer: {len(self.buffer)}/{self.capacity})")
            self.condition.notify()
            return item

# Create buffer
buffer = BoundedBuffer(capacity=3)

def producer_thread():
    """Producer thread"""
    for i in range(6):
        time.sleep(random.uniform(0.3, 0.8))
        buffer.produce(f"Item-{i}")

def consumer_thread():
    """Consumer thread"""
    for i in range(6):
        time.sleep(random.uniform(0.5, 1.2))
        buffer.consume()

print("=" * 40)
print("PRODUCER-CONSUMER WITH CONDITION")
print("=" * 40)

# Create threads
producer = threading.Thread(target=producer_thread)
consumer = threading.Thread(target=consumer_thread)

# Start threads
producer.start()
consumer.start()

# Wait for threads
producer.join()
consumer.join()

print("\nAll items produced and consumed!")

print("""
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ CONDITION VARIABLE ADVANTAGES:                                │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ • Wait for specific conditions                               │
│ • Efficient (no busy waiting)                                │
│ • Combines lock with notification                            │
│ • Perfect for producer-consumer                             │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ CONDITION VARIABLE DISADVANTAGES:                            │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ • More complex than simple locks                             │
│ • Must use `wait()` in a loop                               │
│ • Spurious wakeups possible                                  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
""")

Condition variable key points:

  • Wait/Notify — threads wait for conditions
  • No busy waiting — efficient waiting
  • Producer-consumer — classic use case
  • Loop check — handle spurious wakeups

Quick Check: What is a condition variable used for? (Answer: Allowing threads to wait for specific conditions to be met)

Events

6

Thread Signaling with Events

An event is a simple synchronization object that allows threads to signal each other. Threads can wait for an event to be set, making it perfect for one-time signaling.

# ============================================================
# EVENT DEMONSTRATION
# ============================================================

import threading
import time
import random

# Create event
event = threading.Event()

def waiter(name):
    """Thread that waits for the event"""
    print(f"{name}: Waiting for event...")
    event.wait()  # Block until event is set
    print(f"{name}: Event received! Proceeding...")
    # Do work
    time.sleep(random.uniform(0.3, 0.6))
    print(f"{name}: Work completed.")

def setter():
    """Thread that sets the event after a delay"""
    print("Setter: Sleeping for 2 seconds...")
    time.sleep(2)
    print("Setter: Setting event now!")
    event.set()  # Signal all waiting threads
    print("Setter: Event set. All waiters can proceed.")

print("=" * 40)
print("EVENT DEMONSTRATION")
print("=" * 40)

# Create waiting threads
waiters = []
for i in range(3):
    t = threading.Thread(target=waiter, args=(f"Waiter-{i}",))
    waiters.append(t)
    t.start()

# Create setter thread
setter_thread = threading.Thread(target=setter)
setter_thread.start()

# Wait for all threads
for t in waiters:
    t.join()
setter_thread.join()

print("All threads completed!")

# ============================================================
# EVENT WITH TIMEOUT
# ============================================================

print("\n" + "=" * 40)
print("EVENT WITH TIMEOUT")
print("=" * 40)

event2 = threading.Event()

def waiter_with_timeout(name):
    """Wait with timeout"""
    print(f"{name}: Waiting (timeout 3s)...")
    if event2.wait(timeout=3):
        print(f"{name}: Event received!")
    else:
        print(f"{name}: Timeout! Continuing anyway...")

# Create waiter that will timeout
t = threading.Thread(target=waiter_with_timeout, args=("Timeout Waiter",))
t.start()
t.join()

print("""
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ EVENT ADVANTAGES:                                             │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ • Simple signaling mechanism                                 │
│ • One-to-many notification                                   │
│ • Can set timeout                                            │
│ • Low overhead                                               │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ EVENT DISADVANTAGES:                                         │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ • One-time use (must reset)                                  │
│ • No way to clear for reuse                                 │
│ • Only works with `wait()`                                  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
""")

Event key points:

  • Signaling — threads signal each other
  • One-time — event is cleared after use
  • Timeout — can wait with timeout
  • Simple — easy to understand

Quick Check: What is an event used for? (Answer: Signaling between threads)

Barriers

7

Synchronizing Thread Groups

A barrier synchronizes a fixed number of threads. Threads wait at the barrier until all threads have reached it, then they all proceed together.

# ============================================================
# BARRIER DEMONSTRATION
# ============================================================

import threading
import time
import random

# Barrier for 3 threads
barrier = threading.Barrier(3, action=lambda: print("  ⚔ All threads reached barrier! ⚔"))

def worker(worker_id):
    """Worker thread that uses a barrier"""
    print(f"Worker {worker_id}: Phase 1 - preparing...")
    time.sleep(random.uniform(0.5, 1.5))
    print(f"Worker {worker_id}: Phase 1 complete, waiting at barrier...")
    
    # Wait at barrier
    barrier.wait()
    
    print(f"Worker {worker_id}: Phase 2 - executing...")
    time.sleep(random.uniform(0.3, 0.8))
    print(f"Worker {worker_id}: Phase 2 complete!")

print("=" * 40)
print("BARRIER DEMONSTRATION")
print("=" * 40)

print("Barrier for 3 threads")
print()

# Create 3 workers
workers = []
for i in range(3):
    t = threading.Thread(target=worker, args=(i,))
    workers.append(t)
    t.start()

# Wait for all workers
for t in workers:
    t.join()

print("\nAll workers completed!")

# ============================================================
# BARRIER WITH TIMEOUT
# ============================================================

print("\n" + "=" * 40)
print("BARRIER WITH TIMEOUT")
print("=" * 40)

barrier_timeout = threading.Barrier(3, timeout=2)

def slow_worker(worker_id):
    """Worker that might timeout"""
    delay = random.uniform(0.5, 2.5)
    print(f"Worker {worker_id}: Starting (delay: {delay:.2f}s)")
    time.sleep(delay)
    print(f"Worker {worker_id}: Reached barrier")
    try:
        barrier_timeout.wait()
        print(f"Worker {worker_id}: Passed barrier!")
    except threading.BrokenBarrierError:
        print(f"Worker {worker_id}: Barrier broken (timeout)!")

# Create workers (some may timeout)
workers2 = []
for i in range(3):
    t = threading.Thread(target=slow_worker, args=(i,))
    workers2.append(t)
    t.start()

for t in workers2:
    t.join()

print("""
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ BARRIER ADVANTAGES:                                           │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ • Synchronizes multiple threads                              │
│ • All threads proceed together                               │
│ • Can have action on completion                              │
│ • Supports timeout                                           │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ BARRIER DISADVANTAGES:                                       │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ • Fixed number of threads                                    │
│ • Broken barrier on timeout                                 │
│ • Not reusable after break                                  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
""")

Barrier key points:

  • Group synchronization — all threads together
  • Fixed count — exactly N threads
  • Action function — called when all reach
  • Timeout — broken barrier on timeout

Quick Check: What does a barrier do? (Answer: It synchronizes a group of threads at a specific point)

Synchronization Comparison

8

Choosing the Right Synchronization Tool

Tool Use Case Complexity Threads
Lock Exclusive resource access Low 1 at a time
RLock Nested lock requirements Low 1 at a time (reentrant)
Semaphore Limited resource pools Medium N at a time
Condition Complex waiting conditions High Variable
Event One-time signaling Low All waiting
Barrier Group synchronization Medium Fixed N

Selection guide:

  • Lock — simple exclusive access
  • RLock — when functions call each other with locks
  • Semaphore — connection pools, resource limits
  • Condition — producer-consumer patterns
  • Event — one-time notifications
  • Barrier — parallel processing stages

Quick Check: What would you use for a connection pool? (Answer: A Semaphore)

Best Practices

9

Synchronization Best Practices

# ============================================================
# SYNCHRONIZATION BEST PRACTICES
# ============================================================

print("1. USE CONTEXT MANAGERS")
print("   - `with lock:` for automatic release")
print("   - Prevents forgotten releases")
print("   - Even on exceptions")

print("\n2. MINIMIZE LOCKED SECTIONS")
print("   - Keep critical sections small")
print("   - Only lock what's needed")
print("   - Improves performance")

print("\n3. AVOID DEADLOCKS")
print("   - Acquire locks in consistent order")
print("   - Use timeouts where possible")
print("   - Avoid nested locks if possible")

print("\n4. USE APPROPRIATE TOOLS")
print("   - Lock for simple exclusive access")
print("   - Semaphore for resource pools")
print("   - Condition for complex waiting")
print("   - Event for signaling")

print("\n5. HANDLE EXCEPTIONS")
print("   - Ensure locks are released")
print("   - Use try/finally blocks")
print("   - Context managers handle this")

print("\n6. AVOID BUSY WAITING")
print("   - Use condition.wait() not loops")
print("   - Events and semaphores are efficient")
print("   - Let threads sleep when waiting")

print("\n7. TEST FOR RACE CONDITIONS")
print("   - Use stress testing")
print("   - Add randomization")
print("   - Use ThreadSanitizer if available")

print("\n8. DOCUMENT SYNCHRONIZATION")
print("   - Explain what each lock protects")
print("   - Document ordering requirements")
print("   - Helps maintainability")

Best practices summary:

  • Context managers — `with lock:` for safety
  • Minimize locks — keep critical sections small
  • Avoid deadlocks — consistent order, timeouts
  • Choose right tool — match tool to use case

Quick Check: What is the recommended way to use locks? (Answer: With context managers like `with lock:`)

Try It Yourself

Experiment with thread synchronization in the editor below.

Loading Pyodide... 0%
Python Code Editor
========================================
THREAD SYNCHRONIZATION - PRACTICE
========================================

1. LOCK
----------------------------------------
Counter with lock: 5000 (expected: 5000)

2. SEMAPHORE
----------------------------------------
T0: waiting...
T0: using resource
T1: waiting...
T1: using resource
T0: done
T2: waiting...
T2: using resource
T1: done
T3: waiting...
T3: using resource
T2: done
T3: done

3. EVENT
----------------------------------------
Waiter: waiting for event...
Main: setting event...
Waiter: event received!

Synchronization ensures thread safety!
šŸ†

You've Got It!

You now understand thread synchronization in Python. You know how to use locks, semaphores, condition variables, events, and barriers to write thread-safe code.

Quick Quiz

Test what you've learned:

1. What is a race condition?
2. What is the difference between Lock and RLock?
3. What is a semaphore used for?
4. What is a condition variable best for?
5. What does a barrier do?

Frequently Asked Questions

What is the difference between a Lock and a Semaphore? ā–¼

A Lock provides exclusive access to a resource (only one thread at a time). A Semaphore allows a configurable number of threads to access a resource simultaneously (e.g., N concurrent connections).

What is the difference between Event and Condition? ā–¼

An Event is a one-time signal that wakes all waiting threads. A Condition is more powerful — it allows threads to wait for specific conditions and can be used multiple times with different predicates.

What is a deadlock and how can I prevent it? ā–¼

A deadlock occurs when two or more threads are waiting for each other to release locks. Prevent it by: (1) acquiring locks in a consistent order, (2) using timeouts, (3) using RLock for nested locks, or (4) avoiding nested locks when possible.

What is a common interview question about thread synchronization? ā–¼

Common questions include: "Explain the difference between Lock and Semaphore," "What is a race condition and how do you prevent it?" "Implement a producer-consumer pattern," and "What is deadlock and how do you avoid it?"

When should I use a Barrier? ā–¼

Use a Barrier when you need multiple threads to reach the same point before any of them can continue. Common uses include: (1) parallel processing stages, (2) game starts, (3) simulation synchronization, and (4) distributed computations.

Where to Go From Here

Now that you understand thread synchronization, check out these related topics:

Multi Tasking

Review the basics of multi-tasking in Python.

Review →

Threads Assignments

Practice your threading skills with exercises.

Practice Now →

Concurrent Programming & GIL

Learn about the GIL and its impact on concurrency.

Learn More →
Interview Resources
  • Python Syntax & Variables Interview Questions
  • Top SQL Interview Questions & Answers
  • SQL Joins: Displaying Data from Multiple Tables FAQ
  • Python Lists and Dictionaries Interview Questions
  • Python OOP Interview Questions
  • SQL Set Operators Interview Questions
Previous: Multi Tasking Next: Threads Assignments →