CodeNebula
Python⚡Concurrencyintermediate

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

snippet.py
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

Full Example

shared_state.py
# 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
Run command
python3 shared_state.py