Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
203: Thread Synchronization with Locks
I've seen this exact bug trip up some of the most senior developers I know. You write code that looks mathematically perfect, you run it with a single thread, it works every time. Then you scale it up to handle more concurrency, and suddenly, your data starts "leaking" or numbers don't add up, but only 2% of the time. These are race conditions, and they are a nightmare to debug because they are non-deterministic.
The Mystery of the Disappearing Dollars
Let's look at a typical scenario: a simple bank account balance. In the code below, I'm simulating two different threads trying to deposit money into the same account simultaneously.
import threading
class BankAccount:
def __init__(self):
self.balance = 0
def deposit(self, amount):
# Pretend there's some slight processing delay here
current_balance = self.balance
self.balance = current_balance + amount
account = BankAccount()
def worker():
for _ in range(100000):
account.deposit(1)
threads = [threading.Thread(target=worker) for _ in range(2)]
for t in threads: t.start()
for t in threads: t.join()
print(f"Final Balance: {account.balance}")
# Expected: 200000
# Actual: Something like 142384
If you run this, you'll notice the final balance is almost never 200,000. You might wonder why this happens in Python, given that we have the Global Interpreter Lock (GIL). Here is the catch: the GIL prevents multiple threads from executing Python bytecode at the exact same time, but it doesn't stop the OS from switching threads in the middle of a Python operation.
The line self.balance = current_balance + amount isn't one single action. Python has to read the value, add to it, and then write it back. If Thread A reads the balance (say, 100), and then the OS pauses it to let Thread B run, Thread B might also read 100, add 1, and save 101. When Thread A wakes back up, it still thinks the balance is 100, adds 1, and saves 101. We just lost a whole deposit.
Stopping the Race with threading.Lock
To fix this, we need a way to tell Python: "While I am updating this specific variable, nobody else is allowed to touch it." This is where a Lock (or Mutex) comes in. Think of it like a bathroom key at a coffee shop; if you have the key, you're the only one in the room. Everyone else has to wait in line until you bring the key back.
import threading
class BankAccount:
def __init__(self):
self.balance = 0
# We create the lock as part of the object's state
self.lock = threading.Lock()
def deposit(self, amount):
# The 'with' statement automatically acquires and releases the lock
with self.lock:
current_balance = self.balance
self.balance = current_balance + amount
account = BankAccount()
def worker():
for _ in range(100000):
account.deposit(1)
threads = [threading.Thread(target=worker) for _ in range(2)]
for t in threads: t.start()
for t in threads: t.join()
print(f"Final Balance: {account.balance}")
# Result: 200000 (Every single time)
By wrapping the balance update in with self.lock:, we ensure that the "read-modify-write" cycle is atomic. If Thread B hits that line while Thread A is still inside the block, Thread B will simply stop and wait until Thread A exits the block. Only then will Thread B acquire the lock and perform its update.
One word of caution: don't put too much code inside a lock. The more time a thread spends holding a lock, the more other threads are sitting idle, which kills the performance gains you wanted from using threads in the first place. Keep your locked sections as small and fast as possible.
📋 Practical Task
Building a Thread-Safe Warehouse Inventory Manager
You are tasked with creating an inventory system for a high-volume warehouse. Multiple "Picker" threads are removing items from the stock, and multiple "Restocker" threads are adding items. Without synchronization, the inventory count will become corrupted.
Requirements:
- Create a class
WarehouseInventorythat tracks a dictionary of items (e.g.,{"widgets": 100, "gadgets": 100}). - Implement a method
update_stock(item_name, amount)that addsamountto the item's current count (use negative numbers for removals). - Use a
threading.Lockto ensure that updates to the inventory are thread-safe. - Create 5 "Restocker" threads that each add 1,000 units to "widgets" and 5 "Picker" threads that each remove 1,000 units from "widgets".
- Verify that the final count for "widgets" returns exactly to its starting value.
There are no comments for now.