Rule
Ensure thread-safe access to shared state.
Shared mutable state accessed by multiple threads
without synchronization causes race conditions and runtime errors.
Supported languages: Python, Java, C#Introduction
When multiple threads access and modify shared variables without synchronization, race conditions occur. The final value depends on unpredictable thread execution timing, leading to data corruption, incorrect calculations, or runtime errors. A counter incremented by multiple threads without locking will miss updates as threads read stale values, increment them, and write back conflicting results.
Why it matters
Data corruption and incorrect results: Race conditions cause silent data corruption where values become inconsistent or incorrect. Account balances can be wrong, inventory counts can be negative, or aggregated statistics can be corrupted. These bugs are difficult to reproduce because they depend on exact thread timing.
System instability: Unsynchronized access to shared state can crash applications. One thread might modify a data structure while another reads it, causing exceptions like null pointer errors or index out of bounds. In production, these manifest as intermittent crashes under load.
Debugging complexity: Race conditions are notoriously difficult to debug because they're non-deterministic. The bug might not appear in single-threaded tests or low-load environments. Reproduction requires specific thread interleaving that's hard to force, making issues appear and disappear randomly.
Code examples
❌ Non-compliant:
class BankAccount:
def __init__(self):
self.balance = 0
def deposit(self, amount):
current = self.balance
# Race condition: another thread can modify balance here
time.sleep(0.001) # Simulates processing time
self.balance = current + amount
def withdraw(self, amount):
if self.balance >= amount:
current = self.balance
time.sleep(0.001)
self.balance = current - amount
return True
return False
Why it's wrong: Multiple threads calling deposit() or withdraw() simultaneously create race conditions. Two threads depositing $100 each might both read balance as $0, then both write $100, resulting in final balance of $100 instead of $200.
✅ Compliant:
import threading
class BankAccount:
def __init__(self):
self.__balance = 0
self.__lock = threading.Lock()
@property
def balance(self):
with self.__lock:
return self.__balance
def deposit(self, amount):
with self.__lock:
current = self.__balance
time.sleep(0.001)
self.__balance = current + amount
def withdraw(self, amount):
with self.__lock:
if self.__balance >= amount:
current = self.__balance
time.sleep(0.001)
self.__balance = current - amount
return True
return False
Why this matters: The threading.Lock() ensures only one thread accesses balance at a time. When one thread holds the lock, others wait, preventing simultaneous modifications. Private __balance with readonly @property prevents external code from bypassing the lock protection.
Conclusion
Protect all shared mutable state with appropriate synchronization primitives like locks, semaphores, or atomic operations. Prefer immutable data structures or thread-local storage when possible. When synchronization is necessary, minimize critical sections to reduce contention and improve performance.
.avif)
