Shared State and Thread Safety
Python · Concurrency · Run with python3
Explanation
Sharing mutable state between threads without synchronization causes race conditions. Use `threading.Lock` to protect shared state. `queue.Queue` is inherently thread-safe — prefer it over passing data via shared variables. Immutable objects (tuples, frozensets, numbers) need no locking. `collections.deque` is thread-safe for `appendleft`/`popleft` from different ends. Avoid `global` mutable state in threaded code — encapsulate in locked objects or thread-local storage.
Real-World Scenario
A counter tracking requests processed by multiple threads: `counter += 1` is not atomic in Python (read + increment + write are three bytecode operations). A race condition drops counts. `threading.Lock()` or `threading.atomic` (3.13+) fixes it. ```python import threading class ThreadSafeCounter: def __init__(self) -> None: self._value = 0 self._lock = threading.Lock() def increment(self) -> None: with self._lock: self._value += 1 @property def value(self) -> int: with self._lock: return self._value counter = ThreadSafeCounter() def worker(n: int) -> None: for _ in range(n): counter.increment() threads = [threading.Thread(target=worker, args=(1000,)) for _ in range(10)] for t in threads: t.start() for t in threads: t.join() print(f"Counter: {counter.value}") # Always 10000 with the lock ```
Quick Snippet
import threading
class ThreadSafeCounter:
def __init__(self) -> None:
self._value = 0
self._lock = threading.Lock()
def increment(self) -> None:
with self._lock:
self._value += 1
@property
def value(self) -> int:
with self._lock:
return self._value
counter = ThreadSafeCounter()
def worker(n: int) -> None:
for _ in range(n):
counter.increment()
threads = [threading.Thread(target=worker, args=(1000,)) for _ in range(10)]
for t in threads: t.start()
for t in threads: t.join()
print(f"Counter: {counter.value}") # Always 10000 with the lockFull Example
# Compile: python3 shared_state.py
# Output:
# Counter: 10000
import threading
class ThreadSafeCounter:
def __init__(self) -> None:
self._value = 0
self._lock = threading.Lock()
def increment(self) -> None:
with self._lock:
self._value += 1
@property
def value(self) -> int:
with self._lock:
return self._value
counter = ThreadSafeCounter()
def worker(n: int) -> None:
for _ in range(n):
counter.increment()
threads = [threading.Thread(target=worker, args=(1000,)) for _ in range(10)]
for t in threads: t.start()
for t in threads: t.join()
print(f"Counter: {counter.value}") # Always 10000 with the lock
python3 shared_state.py